diff --git a/crates/bevy_asset/src/io/mod.rs b/crates/bevy_asset/src/io/mod.rs index dbe559d5f2..ca1802c34a 100644 --- a/crates/bevy_asset/src/io/mod.rs +++ b/crates/bevy_asset/src/io/mod.rs @@ -130,7 +130,10 @@ where /// API, where asset bytes and asset metadata bytes are both stored and accessible for a given /// `path`. This trait is not object safe, if needed use a dyn [`ErasedAssetReader`] instead. /// -/// Also see [`AssetWriter`]. +/// This trait defines asset-agnostic mechanisms to read bytes from a storage system. +/// For the per-asset-type saving/loading logic, see [`AssetSaver`](crate::saver::AssetSaver) and [`AssetLoader`](crate::loader::AssetLoader). +/// +/// For a complementary version of this trait that can write assets to storage, see [`AssetWriter`]. pub trait AssetReader: Send + Sync + 'static { /// Returns a future to load the full file data at the provided path. /// @@ -261,7 +264,10 @@ pub enum AssetWriterError { /// API, where asset bytes and asset metadata bytes are both stored and accessible for a given /// `path`. This trait is not object safe, if needed use a dyn [`ErasedAssetWriter`] instead. /// -/// Also see [`AssetReader`]. +/// This trait defines asset-agnostic mechanisms to write bytes to a storage system. +/// For the per-asset-type saving/loading logic, see [`AssetSaver`](crate::saver::AssetSaver) and [`AssetLoader`](crate::loader::AssetLoader). +/// +/// For a complementary version of this trait that can read assets from storage, see [`AssetReader`]. pub trait AssetWriter: Send + Sync + 'static { /// Writes the full asset bytes at the provided path. fn write<'a>( diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index b42b4436f9..0a9dc1ae17 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -1,3 +1,143 @@ +//! In the context of game development, an "asset" is a piece of content that is loaded from disk and displayed in the game. +//! Typically, these are authored by artists and designers (in contrast to code), +//! are relatively large in size, and include everything from textures and models to sounds and music to levels and scripts. +//! +//! This presents two main challenges: +//! - Assets take up a lot of memory; simply storing a copy for each instance of an asset in the game would be prohibitively expensive. +//! - Loading assets from disk is slow, and can cause long load times and delays. +//! +//! These problems play into each other, for if assets are expensive to store in memory, +//! then larger game worlds will need to load them from disk as needed, ideally without a loading screen. +//! +//! As is common in Rust, non-blocking asset loading is done using `async`, with background tasks used to load assets while the game is running. +//! Bevy coordinates these tasks using the [`AssetServer`] resource, storing each loaded asset in a strongly-typed [`Assets`] collection (also a resource). +//! [`Handle`]s serve as an id-based reference to entries in the [`Assets`] collection, allowing them to be cheaply shared between systems, +//! and providing a way to initialize objects (generally entities) before the required assets are loaded. +//! In short: [`Handle`]s are not the assets themselves, they just tell how to look them up! +//! +//! ## Loading assets +//! +//! The [`AssetServer`] is the main entry point for loading assets. +//! Typically, you'll use the [`AssetServer::load`] method to load an asset from disk, which returns a [`Handle`]. +//! Note that this method does not attempt to reload the asset if it has already been loaded: as long as at least one handle has not been dropped, +//! calling [`AssetServer::load`] on the same path will return the same handle. +//! The handle that's returned can be used to instantiate various [`Component`](bevy_ecs::prelude::Component)s that require asset data to function, +//! which will then be spawned into the world as part of an entity. +//! +//! To avoid assets "popping" into existence, you may want to check that all of the required assets are loaded before transitioning to a new scene. +//! This can be done by checking the [`LoadState`] of the asset handle using [`AssetServer::is_loaded_with_dependencies`], +//! which will be `true` when the asset is ready to use. +//! +//! Keep track of what you're waiting on by using a [`HashSet`] of asset handles or similar data structure, +//! which iterate over and poll in your update loop, and transition to the new scene once all assets are loaded. +//! Bevy's built-in states system can be very helpful for this! +//! +//! # Modifying entities that use assets +//! +//! If we later want to change the asset data a given component uses (such as changing an entity's material), we have three options: +//! +//! 1. Change the handle stored on the responsible component to the handle of a different asset +//! 2. Despawn the entity and spawn a new one with the new asset data. +//! 3. Use the [`Assets`] collection to directly modify the current handle's asset data +//! +//! The first option is the most common: just query for the component that holds the handle, and mutate it, pointing to the new asset. +//! Check how the handle was passed in to the entity when it was spawned: if a mesh-related component required a handle to a mesh asset, +//! you'll need to find that component via a query and change the handle to the new mesh asset. +//! This is so commonly done that you should think about strategies for how to store and swap handles in your game. +//! +//! The second option is the simplest, but can be slow if done frequently, +//! and can lead to frustrating bugs as references to the old entity (such as what is targeting it) and other data on the entity are lost. +//! Generally, this isn't a great strategy. +//! +//! The third option has different semantics: rather than modifying the asset data for a single entity, it modifies the asset data for *all* entities using this handle. +//! While this might be what you want, it generally isn't! +//! +//! # Hot reloading assets +//! +//! Bevy supports asset hot reloading, allowing you to change assets on disk and see the changes reflected in your game without restarting. +//! When enabled, any changes to the underlying asset file will be detected by the [`AssetServer`], which will then reload the asset, +//! mutating the asset data in the [`Assets`] collection and thus updating all entities that use the asset. +//! While it has limited uses in published games, it is very useful when developing, as it allows you to iterate quickly. +//! +//! To enable asset hot reloading on desktop platforms, enable `bevy`'s `file_watcher` cargo feature. +//! To toggle it at runtime, you can use the `watch_for_changes_override` field in the [`AssetPlugin`] to enable or disable hot reloading. +//! +//! # Procedural asset creation +//! +//! Not all assets are loaded from disk: some are generated at runtime, such as procedural materials, sounds or even levels. +//! After creating an item of a type that implements [`Asset`], you can add it to the [`Assets`] collection using [`Assets::add`]. +//! Once in the asset collection, this data can be operated on like any other asset. +//! +//! Note that, unlike assets loaded from a file path, no general mechanism currently exists to deduplicate procedural assets: +//! calling [`Assets::add`] for every entity that needs the asset will create a new copy of the asset for each entity, +//! quickly consuming memory. +//! +//! ## Handles and reference counting +//! +//! [`Handle`] (or their untyped counterpart [`UntypedHandle`]) are used to reference assets in the [`Assets`] collection, +//! and are the primary way to interact with assets in Bevy. +//! As a user, you'll be working with handles a lot! +//! +//! The most important thing to know about handles is that they are reference counted: when you clone a handle, you're incrementing a reference count. +//! When the object holding the handle is dropped (generally because an entity was despawned), the reference count is decremented. +//! When the reference count hits zero, the asset it references is removed from the [`Assets`] collection. +//! +//! This reference counting is a simple, largely automatic way to avoid holding onto memory for game objects that are no longer in use. +//! However, it can lead to surprising behavior if you're not careful! +//! +//! There are two categories of problems to watch out for: +//! - never dropping a handle, causing the asset to never be removed from memory +//! - dropping a handle too early, causing the asset to be removed from memory while it's still in use +//! +//! The first problem is less critical for beginners, as for tiny games, you can often get away with simply storing all of the assets in memory at once, +//! and loading them all at the start of the game. +//! As your game grows, you'll need to be more careful about when you load and unload assets, +//! segmenting them by level or area, and loading them on-demand. +//! This problem generally arises when handles are stored in a persistent "collection" or "manifest" of possible objects (generally in a resource), +//! which is convenient for easy access and zero-latency spawning, but can result in high but stable memory usage. +//! +//! The second problem is more concerning, and looks like your models or textures suddenly disappearing from the game. +//! Debugging reveals that the *entities* are still there, but nothing is rendering! +//! This is because the assets were removed from memory while they were still in use. +//! You were probably too aggressive with the use of weak handles (which don't increment the reference count of the asset): think through the lifecycle of your assets carefully! +//! As soon as an asset is loaded, you must ensure that at least one strong handle is held to it until all matching entities are out of sight of the player. +//! +//! # Asset dependencies +//! +//! Some assets depend on other assets to be loaded before they can be loaded themselves. +//! For example, a 3D model might require both textures and meshes to be loaded, +//! or a 2D level might require a tileset to be loaded. +//! +//! The assets that are required to load another asset are called "dependencies". +//! An asset is only considered fully loaded when it and all of its dependencies are loaded. +//! Asset dependencies can be declared when implementing the [`Asset`] trait by implementing the [`VisitAssetDependencies`] trait, +//! and the `#[dependency]` attribute can be used to automatically derive this implementation. +//! +//! # Custom asset types +//! +//! While Bevy comes with implementations for a large number of common game-oriented asset types (often behind off-by-default feature flags!), +//! implementing a custom asset type can be useful when dealing with unusual, game-specific, or proprietary formats. +//! +//! Defining a new asset type is as simple as implementing the [`Asset`] trait. +//! This requires [`TypePath`] for metadata about the asset type, +//! and [`VisitAssetDependencies`] to track asset dependencies. +//! In simple cases, you can derive [`Asset`] and [`Reflect`] and be done with it: the required supertraits will be implemented for you. +//! +//! With a new asset type in place, we now need to figure out how to load it. +//! While [`AssetReader`](io::AssetReader) describes strategies to read asset bytes from various sources, +//! [`AssetLoader`] is the trait that actually turns those into your desired in-memory format. +//! Generally, (only) [`AssetLoader`] needs to be implemented for custom assets, as the [`AssetReader`](io::AssetReader) implementations are provided by Bevy. +//! +//! However, [`AssetLoader`] shouldn't be implemented for your asset type directly: instead, this is implemented for a "loader" type +//! that can store settings and any additional data required to load your asset, while your asset type is used as the [`AssetLoader::Asset`] associated type. +//! As the trait documentation explains, this allows various [`AssetLoader::Settings`] to be used to configure the loader. +//! +//! After the loader is implemented, it needs to be registered with the [`AssetServer`] using [`App::register_asset_loader`](AssetApp::register_asset_loader). +//! Once your asset type is loaded, you can use it in your game like any other asset type! +//! +//! If you want to save your assets back to disk, you should implement [`AssetSaver`](saver::AssetSaver) as well. +//! This trait mirrors [`AssetLoader`] in structure, and works in tandem with [`AssetWriter`](io::AssetWriter), which mirrors [`AssetReader`](io::AssetReader). + // FIXME(3492): remove once docs are ready #![allow(missing_docs)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] @@ -240,6 +380,13 @@ impl Plugin for AssetPlugin { } } +/// Declares that this type is an asset, +/// which can be loaded and managed by the [`AssetServer`] and stored in [`Assets`] collections. +/// +/// Generally, assets are large, complex, and/or expensive to load from disk, and are often authored by artists or designers. +/// +/// [`TypePath`] is largely used for diagnostic purposes, and should almost always be implemented by deriving [`Reflect`] on your type. +/// [`VisitAssetDependencies`] is used to track asset dependencies, and an implementation is automatically generated when deriving [`Asset`]. #[diagnostic::on_unimplemented( message = "`{Self}` is not an `Asset`", label = "invalid `Asset`", @@ -247,6 +394,10 @@ impl Plugin for AssetPlugin { )] pub trait Asset: VisitAssetDependencies + TypePath + Send + Sync + 'static {} +/// This trait defines how to visit the dependencies of an asset. +/// For example, a 3D model might require both textures and meshes to be loaded. +/// +/// Note that this trait is automatically implemented when deriving [`Asset`]. pub trait VisitAssetDependencies { fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)); } diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index f0dce3593d..f8521b4cda 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -20,6 +20,10 @@ use thiserror::Error; /// Loads an [`Asset`] from a given byte [`Reader`]. This can accept [`AssetLoader::Settings`], which configure how the [`Asset`] /// should be loaded. +/// +/// This trait is generally used in concert with [`AssetReader`](crate::io::AssetReader) to load assets from a byte source. +/// +/// For a complementary version of this trait that can save assets, see [`AssetSaver`](crate::saver::AssetSaver). pub trait AssetLoader: Send + Sync + 'static { /// The top level [`Asset`] loaded by this [`AssetLoader`]. type Asset: Asset; diff --git a/crates/bevy_asset/src/saver.rs b/crates/bevy_asset/src/saver.rs index 4d5925dc54..115a880377 100644 --- a/crates/bevy_asset/src/saver.rs +++ b/crates/bevy_asset/src/saver.rs @@ -8,6 +8,10 @@ use std::{borrow::Borrow, hash::Hash, ops::Deref}; /// Saves an [`Asset`] of a given [`AssetSaver::Asset`] type. [`AssetSaver::OutputLoader`] will then be used to load the saved asset /// in the final deployed application. The saver should produce asset bytes in a format that [`AssetSaver::OutputLoader`] can read. +/// +/// This trait is generally used in concert with [`AssetWriter`](crate::io::AssetWriter) to write assets as bytes. +/// +/// For a complementary version of this trait that can load assets, see [`AssetLoader`]. pub trait AssetSaver: Send + Sync + 'static { /// The top level [`Asset`] saved by this [`AssetSaver`]. type Asset: Asset; diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index a9e5fcddbf..1f73f951a8 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -266,6 +266,9 @@ impl AssetServer { /// it returns a "strong" [`Handle`]. When the [`Asset`] is loaded (and enters [`LoadState::Loaded`]), it will be added to the /// associated [`Assets`] resource. /// + /// Note that if the asset at this path is already loaded, this function will return the existing handle, + /// and will not waste work spawning a new load task. + /// /// In case the file path contains a hashtag (`#`), the `path` must be specified using [`Path`] /// or [`AssetPath`] because otherwise the hashtag would be interpreted as separator between /// the file path and the label. For example: