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 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<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")]
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<Mutex<Vec<HandleId>>>,
}
/// 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<dyn AssetIo>,
pub(crate) asset_ref_counter: AssetRefCounter,
@ -58,17 +75,45 @@ pub struct AssetServerInternal {
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)]
pub struct AssetServer {
pub(crate) server: Arc<AssetServerInternal>,
}
impl AssetServer {
/// Creates a new asset server with the provided asset I/O.
pub fn new<T: AssetIo>(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<dyn AssetIo>) -> 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<T>(&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<T: Asset, I: Into<HandleId>>(&self, id: I) -> Handle<T> {
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<I: Into<HandleId>>(&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<H: Into<HandleId>>(&self, handle: H) -> Option<AssetPath<'_>> {
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<H: Into<HandleId>>(&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<Item = HandleId>) -> 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 <https://doc.rust-lang.org/cargo/reference/environment-variables.html>)
/// 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<AssetPath<'a>>>(&self, path: P) -> Handle<T> {
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<AssetPath<'a>>>(&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<P: AsRef<Path>>(
&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<AssetServer>) {
free_unused_assets_system_impl(&asset_server);
}

View file

@ -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<T: Asset> {
#[allow(missing_docs)]
Created { handle: Handle<T> },
#[allow(missing_docs)]
Modified { handle: Handle<T> },
#[allow(missing_docs)]
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.
///
/// # Events
///
/// * [`AssetEvent::Created`]
pub fn add(&mut self, asset: T) -> Handle<T> {
let id = HandleId::random::<T>();
@ -110,8 +115,9 @@ impl<T: Asset> Assets<T> {
/// 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<H: Into<HandleId>>(&mut self, handle: H, asset: T) {
let id: HandleId = handle.into();
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
/// 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)
}
/// 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> {
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<H: Into<HandleId>>(
&mut self,
handle: H,
@ -179,12 +186,12 @@ impl<T: Asset> Assets<T> {
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)> {
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)> {
self.assets.iter_mut().map(|(k, v)| {
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> + '_ {
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<H: Into<HandleId>>(&mut self, handle: H) -> Option<T> {
let id: HandleId = handle.into();
@ -238,6 +246,8 @@ impl<T: Asset> Assets<T> {
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<AssetEvent<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 {
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<T>(&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<T: Clone>(&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<T>(&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<T>(&mut self) -> &mut Self
where
T: AssetLoader + FromWorld;
/// Adds the provided asset loader to the application.
fn add_asset_loader<T>(&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<T>(&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 {

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_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<T: Asset> {
/// The collection of asset handles.
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
/// 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<A: Asset>(

View file

@ -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<T: Asset> {
marker: std::marker::PhantomData<T>,
}
@ -24,10 +24,14 @@ impl<T: Asset> Plugin for 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 {
DiagnosticId(T::TYPE_UUID)
}
/// Registers the asset count diagnostic for the current application.
pub fn setup_system(mut diagnostics: ResMut<Diagnostics>) {
let asset_type_name = std::any::type_name::<T>();
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>>) {
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;
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 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<Result<Event>>,
@ -21,6 +23,7 @@ impl Default for FilesystemWatcher {
}
impl FilesystemWatcher {
/// Watch for changes recursively at the provided path.
pub fn watch<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
self.watcher.watch(path.as_ref(), RecursiveMode::Recursive)
}

View file

@ -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<AssetPath<'a>> for HandleId {
}
impl HandleId {
/// Creates a random id for an asset of type `T`.
#[inline]
pub fn random<T: Asset>() -> Self {
HandleId::Id(T::TYPE_UUID, rand::random())
}
/// Creates the default id for an asset of type `T`.
#[inline]
pub fn default<T: Asset>() -> 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<T>
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<T: Asset> Handle<T> {
}
}
/// Creates a weak handle into an Asset identified by `id`.
#[inline]
pub fn weak(id: HandleId) -> 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> {
Handle {
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 {
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<T>) {
if self.is_strong() {
return;
@ -174,12 +182,14 @@ impl<T: Asset> Handle<T> {
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<T: Asset> Handle<T> {
}
}
/// Creates a weak, untyped copy of this handle.
pub fn clone_weak_untyped(&self) -> HandleUntyped {
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
/// `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.
#[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<T: Asset>(mut self) -> Handle<T> {
if let HandleId::Id(type_uuid, _) = self.id {
assert!(

View file

@ -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<AssetMeta>,
}
/// Metadata for an asset.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AssetMeta {
/// Asset label.
pub label: Option<String>,
/// Asset dependencies.
pub dependencies: Vec<AssetPath<'static>>,
/// 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<SourceMeta>,
/// The path of the source.
pub path: PathBuf,
/// A map of assets and their type identifiers.
pub asset_types: HashMap<LabelId, Uuid>,
/// 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<LabelId>,
/// 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<Uuid> {
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,
}

View file

@ -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,
}

View file

@ -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<P: AsRef<Path>>(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"))

View file

@ -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()

View file

@ -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<Vec<u8>, AssetIoError>>;
/// Returns an iterator of directory entry names at the provided path.
fn read_directory(
&self,
path: &Path,
) -> 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>;
/// 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()

View file

@ -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,
}

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 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.

View file

@ -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<T> Asset for T where T: TypeUuid + AssetDynamic + 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(crate) value: Option<T>,
pub(crate) dependencies: Vec<AssetPath<'static>>,
}
impl<T: Asset> LoadedAsset<T> {
/// Creates a new loaded asset.
pub fn new(value: T) -> Self {
Self {
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) {
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<AssetPath<'static>>) -> Self {
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(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<T: Asset>(&mut self, asset: LoadedAsset<T>) {
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> {
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<I: Into<HandleId>, T: Asset>(&self, id: I) -> Handle<T> {
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> {
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> {
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<T> {
/// The asset itself.
pub asset: Box<T>,
/// 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<T> {
/// The sender endpoint of the channel.
pub sender: Sender<AssetLifecycleEvent<T>>,
/// The receiver endpoint of the channel.
pub receiver: Receiver<AssetLifecycleEvent<T>>,
}
/// Events for the [`AssetLifecycleChannel`].
pub enum AssetLifecycleEvent<T> {
/// An asset was created.
Create(AssetResult<T>),
/// 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<dyn AssetDynamic>, version: usize);
/// Notifies the asset server that an asset was freed.
fn free_asset(&self, id: HandleId);
}
impl_downcast!(AssetLifecycle);

View file

@ -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<String>) -> 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<Option<&'a str>> 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
}