//! Implements loader for a Gzip compressed asset. use bevy::{ asset::{ io::{Reader, VecReader}, AssetLoader, ErasedLoadedAsset, LoadContext, LoadDirectError, }, prelude::*, reflect::TypePath, }; use flate2::read::GzDecoder; use std::{io::prelude::*, marker::PhantomData}; use thiserror::Error; #[derive(Asset, TypePath)] struct GzAsset { uncompressed: ErasedLoadedAsset, } #[derive(Default)] struct GzAssetLoader; /// Possible errors that can be produced by [`GzAssetLoader`] #[non_exhaustive] #[derive(Debug, Error)] 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 to 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; async fn load( &self, reader: &mut dyn Reader, _settings: &(), load_context: &mut LoadContext<'_>, ) -> Result { 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 .loader() .with_unknown_type() .immediate() .with_reader(&mut reader) .load(contained_path) .await?; Ok(GzAsset { uncompressed }) } fn extensions(&self) -> &[&str] { &["gz"] } } #[derive(Component, Default)] struct Compressed { compressed: Handle, _phantom: PhantomData, } fn main() { App::new() .add_plugins(DefaultPlugins) .init_asset::() .init_asset_loader::() .add_systems(Startup, setup) .add_systems(Update, decompress::) .run(); } fn setup(mut commands: Commands, asset_server: Res) { commands.spawn(Camera2d); commands.spawn(Compressed:: { compressed: asset_server.load("data/compressed_image.png.gz"), ..default() }); } fn decompress>, A: Asset>( mut commands: Commands, asset_server: Res, mut compressed_assets: ResMut>, query: Query<(Entity, &Compressed)>, ) { for (entity, Compressed { compressed, .. }) in query.iter() { let Some(GzAsset { uncompressed }) = compressed_assets.remove(compressed) else { continue; }; let uncompressed = uncompressed.take::().unwrap(); commands .entity(entity) .remove::>() .insert(T::from(asset_server.add(uncompressed))); } }