docs/bevy/examples/asset/asset_decompression.rs 3.8 K · 133 lines · raw · history

1 //! Implements loader for a Gzip compressed asset.
2
3 use bevy::{
4 asset::{
5 io::{Reader, VecReader},
6 AssetLoader, ErasedLoadedAsset, LoadContext, LoadDirectError,
7 },
8 prelude::*,
9 reflect::TypePath,
10 };
11 use flate2::read::GzDecoder;
12 use std::{io::prelude::*, marker::PhantomData};
13 use thiserror::Error;
14
15 #[derive(Asset, TypePath)]
16 struct GzAsset {
17 uncompressed: ErasedLoadedAsset,
18 }
19
20 #[derive(Default, TypePath)]
21 struct GzAssetLoader;
22
23 /// Possible errors that can be produced by [`GzAssetLoader`]
24 #[non_exhaustive]
25 #[derive(Debug, Error)]
26 enum GzAssetLoaderError {
27 /// An [IO](std::io) Error
28 #[error("Could not load asset: {0}")]
29 Io(#[from] std::io::Error),
30 /// An error caused when the asset path cannot be used to determine the uncompressed asset type.
31 #[error("Could not determine file path of uncompressed asset")]
32 IndeterminateFilePath,
33 /// An error caused by the internal asset loader.
34 #[error("Could not load contained asset: {0}")]
35 LoadDirectError(#[from] LoadDirectError),
36 }
37
38 impl AssetLoader for GzAssetLoader {
39 type Asset = GzAsset;
40 type Settings = ();
41 type Error = GzAssetLoaderError;
42
43 async fn load(
44 &self,
45 reader: &mut dyn Reader,
46 _settings: &(),
47 load_context: &mut LoadContext<'_>,
48 ) -> Result<Self::Asset, Self::Error> {
49 let compressed_path = load_context.path();
50 let file_name = compressed_path
51 .path()
52 .file_name()
53 .ok_or(GzAssetLoaderError::IndeterminateFilePath)?
54 .to_string_lossy();
55 let uncompressed_file_name = file_name
56 .strip_suffix(".gz")
57 .ok_or(GzAssetLoaderError::IndeterminateFilePath)?;
58 let contained_path = compressed_path
59 .resolve_embed_str(uncompressed_file_name)
60 .map_err(|_| GzAssetLoaderError::IndeterminateFilePath)?;
61
62 let mut bytes_compressed = Vec::new();
63
64 reader.read_to_end(&mut bytes_compressed).await?;
65
66 let mut decoder = GzDecoder::new(bytes_compressed.as_slice());
67
68 let mut bytes_uncompressed = Vec::new();
69
70 decoder.read_to_end(&mut bytes_uncompressed)?;
71
72 // Now that we have decompressed the asset, let's pass it back to the
73 // context to continue loading
74
75 let mut reader = VecReader::new(bytes_uncompressed);
76
77 let uncompressed = load_context
78 .load_builder()
79 .load_untyped_value_from_reader(contained_path, &mut reader)
80 .await?;
81
82 Ok(GzAsset { uncompressed })
83 }
84
85 fn extensions(&self) -> &[&str] {
86 &["gz"]
87 }
88 }
89
90 #[derive(Component, Default)]
91 struct Compressed<T> {
92 compressed: Handle<GzAsset>,
93 _phantom: PhantomData<T>,
94 }
95
96 fn main() {
97 App::new()
98 .add_plugins(DefaultPlugins)
99 .init_asset::<GzAsset>()
100 .init_asset_loader::<GzAssetLoader>()
101 .add_systems(Startup, setup)
102 .add_systems(Update, decompress::<Sprite, Image>)
103 .run();
104 }
105
106 fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
107 commands.spawn(Camera2d);
108
109 commands.spawn(Compressed::<Image> {
110 compressed: asset_server.load("data/compressed_image.png.gz"),
111 ..default()
112 });
113 }
114
115 fn decompress<T: Component + From<Handle<A>>, A: Asset>(
116 mut commands: Commands,
117 asset_server: Res<AssetServer>,
118 mut compressed_assets: ResMut<Assets<GzAsset>>,
119 query: Query<(Entity, &Compressed<A>)>,
120 ) {
121 for (entity, Compressed { compressed, .. }) in query.iter() {
122 let Some(GzAsset { uncompressed }) = compressed_assets.remove(compressed) else {
123 continue;
124 };
125
126 let uncompressed = uncompressed.take::<A>().unwrap();
127
128 commands
129 .entity(entity)
130 .remove::<Compressed<A>>()
131 .insert(T::from(asset_server.add(uncompressed)));
132 }
133 }