Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Method to Allow Pipelined Asset Loading #10565

Merged
merged 3 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ bevy_internal = { path = "crates/bevy_internal", version = "0.12.0", default-fea
[dev-dependencies]
rand = "0.8.0"
ron = "0.8.0"
flate2 = "1.0"
serde = { version = "1", features = ["derive"] }
bytemuck = "1.7"
# Needed to poll Task examples
Expand Down Expand Up @@ -1077,6 +1078,17 @@ description = "Demonstrates various methods to load assets"
category = "Assets"
wasm = false

[[example]]
name = "asset_decompression"
path = "examples/asset/asset_decompression.rs"
doc-scrape-examples = true

[package.metadata.example.asset_decompression]
name = "Asset Decompression"
description = "Demonstrates loading a compressed asset"
category = "Assets"
wasm = false

[[example]]
name = "custom_asset"
path = "examples/asset/custom_asset.rs"
Expand Down
Binary file added assets/data/compressed_image.png.gz
Binary file not shown.
56 changes: 56 additions & 0 deletions crates/bevy_asset/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,62 @@ impl<'a> LoadContext<'a> {
self.loader_dependencies.insert(path, hash);
Ok(loaded_asset)
}

/// Loads the asset at the given `path` directly from the provided `reader`. This is an async function that will wait until the asset is fully loaded before
/// returning. Use this if you need the _value_ of another asset in order to load the current asset, and that value comes from your [`Reader`].
/// For example, if you are deriving a new asset from the referenced asset, or you are building a collection of assets. This will add the `path` as a
/// "load dependency".
///
/// If the current loader is used in a [`Process`] "asset preprocessor", such as a [`LoadAndSave`] preprocessor,
/// changing a "load dependency" will result in re-processing of the asset.
///
/// [`Process`]: crate::processor::Process
/// [`LoadAndSave`]: crate::processor::LoadAndSave
pub async fn load_direct_with_reader<'b>(
bushrat011899 marked this conversation as resolved.
Show resolved Hide resolved
&mut self,
reader: &mut Reader<'_>,
path: impl Into<AssetPath<'b>>,
) -> Result<ErasedLoadedAsset, LoadDirectError> {
let path = path.into().into_owned();

let loader = self
.asset_server
.get_path_asset_loader(&path)
.await
.map_err(|error| LoadDirectError {
dependency: path.clone(),
error: error.into(),
})?;

let meta = loader.default_meta();

let loaded_asset = self
.asset_server
.load_with_meta_loader_and_reader(
&path,
meta,
&*loader,
reader,
false,
self.populate_hashes,
)
.await
.map_err(|error| LoadDirectError {
dependency: path.clone(),
error,
})?;

let info = loaded_asset
.meta
.as_ref()
.and_then(|m| m.processed_info().as_ref());

let hash = info.map(|i| i.full_hash).unwrap_or_default();

self.loader_dependencies.insert(path, hash);

Ok(loaded_asset)
}
}

/// An error produced when calling [`LoadContext::read_asset_bytes`]
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ Example | Description

Example | Description
--- | ---
[Asset Decompression](../examples/asset/asset_decompression.rs) | Demonstrates loading a compressed asset
[Asset Loading](../examples/asset/asset_loading.rs) | Demonstrates various methods to load assets
[Asset Processing](../examples/asset/processing/asset_processing.rs) | Demonstrates how to process and load custom assets
[Custom Asset](../examples/asset/custom_asset.rs) | Implements a custom asset loader
Expand Down
138 changes: 138 additions & 0 deletions examples/asset/asset_decompression.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//! Implements loader for a Gzip compressed asset.

use bevy::utils::thiserror;
use bevy::{
asset::{
io::{Reader, VecReader},
AssetLoader, AsyncReadExt, ErasedLoadedAsset, LoadContext, LoadDirectError,
},
prelude::*,
reflect::TypePath,
utils::BoxedFuture,
};
use flate2::read::GzDecoder;
use std::io::prelude::*;
use std::marker::PhantomData;
use thiserror::Error;

#[derive(Asset, TypePath)]
pub struct GzAsset {
pub uncompressed: ErasedLoadedAsset,
}

#[derive(Default)]
pub struct GzAssetLoader;

/// Possible errors that can be produced by [`GzAssetLoader`]
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum GzAssetLoaderError {
/// An [IO](std::io) Error
#[error("Could not load asset: {0}")]
Io(#[from] std::io::Error),
/// An error caused when the asset path cannot be used ot determine the uncompressed asset type.
#[error("Could not determine file path of uncompressed asset")]
IndeterminateFilePath,
/// An error caused by the internal asset loader.
#[error("Could not load contained asset: {0}")]
LoadDirectError(#[from] LoadDirectError),
}

impl AssetLoader for GzAssetLoader {
type Asset = GzAsset;
type Settings = ();
type Error = GzAssetLoaderError;
fn load<'a>(
&'a self,
reader: &'a mut Reader,
_settings: &'a (),
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> {
Box::pin(async move {
let compressed_path = load_context.path();
let file_name = compressed_path
.file_name()
.ok_or(GzAssetLoaderError::IndeterminateFilePath)?
.to_string_lossy();
let uncompressed_file_name = file_name
.strip_suffix(".gz")
.ok_or(GzAssetLoaderError::IndeterminateFilePath)?;
let contained_path = compressed_path.join(uncompressed_file_name);

let mut bytes_compressed = Vec::new();

reader.read_to_end(&mut bytes_compressed).await?;

let mut decoder = GzDecoder::new(bytes_compressed.as_slice());

let mut bytes_uncompressed = Vec::new();

decoder.read_to_end(&mut bytes_uncompressed)?;

// Now that we have decompressed the asset, let's pass it back to the
// context to continue loading

let mut reader = VecReader::new(bytes_uncompressed);

let uncompressed = load_context
.load_direct_with_reader(&mut reader, contained_path)
.await?;

Ok(GzAsset { uncompressed })
})
}

fn extensions(&self) -> &[&str] {
&["gz"]
}
}

#[derive(Component, Default)]
struct Compressed<T> {
compressed: Handle<GzAsset>,
_phantom: PhantomData<T>,
}

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_asset::<GzAsset>()
.init_asset_loader::<GzAssetLoader>()
.add_systems(Startup, setup)
.add_systems(Update, decompress::<Image>)
.run();
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2dBundle::default());

commands.spawn((
Compressed::<Image> {
compressed: asset_server.load("data/compressed_image.png.gz"),
..default()
},
Sprite::default(),
TransformBundle::default(),
VisibilityBundle::default(),
));
}

fn decompress<A: Asset>(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut compressed_assets: ResMut<Assets<GzAsset>>,
query: Query<(Entity, &Compressed<A>)>,
) {
for (entity, Compressed { compressed, .. }) in query.iter() {
let Some(GzAsset { uncompressed }) = compressed_assets.remove(compressed) else {
continue;
};

let uncompressed = uncompressed.take::<A>().unwrap();

commands
.entity(entity)
.remove::<Compressed<A>>()
.insert(asset_server.add(uncompressed));
}
}
Loading