|
| 1 | +//! This example shows how to use the temporary asset source, `temp://`. |
| 2 | +//! First, a [`TextAsset`] is created in-memory, then saved into the temporary asset source. |
| 3 | +//! Once the save operation is completed, we load the asset just like any other file, and display its contents! |
| 4 | +
|
| 5 | +use bevy::{ |
| 6 | + asset::{ |
| 7 | + saver::{AssetSaver, ErasedAssetSaver}, |
| 8 | + AssetPath, ErasedLoadedAsset, LoadedAsset, |
| 9 | + }, |
| 10 | + prelude::*, |
| 11 | + tasks::{block_on, IoTaskPool, Task}, |
| 12 | +}; |
| 13 | + |
| 14 | +use futures_lite::future; |
| 15 | +use text_asset::{TextAsset, TextLoader, TextSaver}; |
| 16 | + |
| 17 | +fn main() { |
| 18 | + App::new() |
| 19 | + .add_plugins(DefaultPlugins) |
| 20 | + .init_asset::<TextAsset>() |
| 21 | + .register_asset_loader(TextLoader) |
| 22 | + .add_systems(Startup, (save_temp_asset, setup_ui)) |
| 23 | + .add_systems(Update, (wait_until_temp_saved, display_text)) |
| 24 | + .run(); |
| 25 | +} |
| 26 | + |
| 27 | +/// Attempt to save an asset to the temporary asset source. |
| 28 | +fn save_temp_asset(assets: Res<AssetServer>, mut commands: Commands) { |
| 29 | + // This is the asset we will attempt to save. |
| 30 | + let my_text_asset = TextAsset("Hello World!".to_owned()); |
| 31 | + |
| 32 | + // To ensure the `Task` can outlive this function, we must provide owned versions |
| 33 | + // of the `AssetServer` and our desired path. |
| 34 | + let path = AssetPath::from("temp://message.txt").into_owned(); |
| 35 | + let server = assets.clone(); |
| 36 | + |
| 37 | + let task = IoTaskPool::get().spawn(async move { |
| 38 | + save_asset(my_text_asset, path, server, TextSaver) |
| 39 | + .await |
| 40 | + .unwrap(); |
| 41 | + }); |
| 42 | + |
| 43 | + // To ensure the task completes before we try loading, we will manually poll this task |
| 44 | + // so we can react to its completion. |
| 45 | + commands.spawn(SavingTask(task)); |
| 46 | +} |
| 47 | + |
| 48 | +/// Poll the save tasks until completion, and then start loading our temporary text asset. |
| 49 | +fn wait_until_temp_saved( |
| 50 | + assets: Res<AssetServer>, |
| 51 | + mut tasks: Query<(Entity, &mut SavingTask)>, |
| 52 | + mut commands: Commands, |
| 53 | +) { |
| 54 | + for (entity, mut task) in tasks.iter_mut() { |
| 55 | + if let Some(()) = block_on(future::poll_once(&mut task.0)) { |
| 56 | + commands.insert_resource(MyTempText { |
| 57 | + text: assets.load("temp://message.txt"), |
| 58 | + }); |
| 59 | + |
| 60 | + commands.entity(entity).despawn_recursive(); |
| 61 | + } |
| 62 | + } |
| 63 | +} |
| 64 | + |
| 65 | +/// Setup a basic UI to display our [`TextAsset`] once it's loaded. |
| 66 | +fn setup_ui(mut commands: Commands) { |
| 67 | + commands.spawn(Camera2dBundle::default()); |
| 68 | + |
| 69 | + commands.spawn((TextBundle::from_section("Loading...", default()) |
| 70 | + .with_text_justify(JustifyText::Center) |
| 71 | + .with_style(Style { |
| 72 | + position_type: PositionType::Absolute, |
| 73 | + bottom: Val::Percent(50.), |
| 74 | + right: Val::Percent(50.), |
| 75 | + ..default() |
| 76 | + }),)); |
| 77 | +} |
| 78 | + |
| 79 | +/// Once the [`TextAsset`] is loaded, update our display text to its contents. |
| 80 | +fn display_text( |
| 81 | + mut query: Query<&mut Text>, |
| 82 | + my_text: Option<Res<MyTempText>>, |
| 83 | + texts: Res<Assets<TextAsset>>, |
| 84 | +) { |
| 85 | + let message = my_text |
| 86 | + .as_ref() |
| 87 | + .and_then(|resource| texts.get(&resource.text)) |
| 88 | + .map(|text| text.0.as_str()) |
| 89 | + .unwrap_or("Loading..."); |
| 90 | + |
| 91 | + for mut text in query.iter_mut() { |
| 92 | + *text = Text::from_section(message, default()); |
| 93 | + } |
| 94 | +} |
| 95 | + |
| 96 | +/// Save an [`Asset`] at the provided path. Returns [`None`] on failure. |
| 97 | +async fn save_asset<A: Asset>( |
| 98 | + asset: A, |
| 99 | + path: AssetPath<'_>, |
| 100 | + server: AssetServer, |
| 101 | + saver: impl AssetSaver<Asset = A> + ErasedAssetSaver, |
| 102 | +) -> Option<()> { |
| 103 | + let asset = ErasedLoadedAsset::from(LoadedAsset::from(asset)); |
| 104 | + let source = server.get_source(path.source()).ok()?; |
| 105 | + let writer = source.writer().ok()?; |
| 106 | + |
| 107 | + let mut writer = writer.write(path.path()).await.ok()?; |
| 108 | + ErasedAssetSaver::save(&saver, &mut writer, &asset, &()) |
| 109 | + .await |
| 110 | + .ok()?; |
| 111 | + |
| 112 | + Some(()) |
| 113 | +} |
| 114 | + |
| 115 | +#[derive(Component)] |
| 116 | +struct SavingTask(Task<()>); |
| 117 | + |
| 118 | +#[derive(Resource)] |
| 119 | +struct MyTempText { |
| 120 | + text: Handle<TextAsset>, |
| 121 | +} |
| 122 | + |
| 123 | +mod text_asset { |
| 124 | + //! Putting the implementation of an asset loader and writer for a text asset in this module to avoid clutter. |
| 125 | + //! While this is required for this example to function, it isn't the focus. |
| 126 | +
|
| 127 | + use bevy::{ |
| 128 | + asset::{ |
| 129 | + io::{Reader, Writer}, |
| 130 | + saver::{AssetSaver, SavedAsset}, |
| 131 | + AssetLoader, LoadContext, |
| 132 | + }, |
| 133 | + prelude::*, |
| 134 | + }; |
| 135 | + use futures_lite::{AsyncReadExt, AsyncWriteExt}; |
| 136 | + |
| 137 | + #[derive(Asset, TypePath, Debug)] |
| 138 | + pub struct TextAsset(pub String); |
| 139 | + |
| 140 | + #[derive(Default)] |
| 141 | + pub struct TextLoader; |
| 142 | + |
| 143 | + impl AssetLoader for TextLoader { |
| 144 | + type Asset = TextAsset; |
| 145 | + type Settings = (); |
| 146 | + type Error = std::io::Error; |
| 147 | + async fn load<'a>( |
| 148 | + &'a self, |
| 149 | + reader: &'a mut Reader<'_>, |
| 150 | + _settings: &'a Self::Settings, |
| 151 | + _load_context: &'a mut LoadContext<'_>, |
| 152 | + ) -> Result<TextAsset, Self::Error> { |
| 153 | + let mut bytes = Vec::new(); |
| 154 | + reader.read_to_end(&mut bytes).await?; |
| 155 | + let value = String::from_utf8(bytes).unwrap(); |
| 156 | + Ok(TextAsset(value)) |
| 157 | + } |
| 158 | + |
| 159 | + fn extensions(&self) -> &[&str] { |
| 160 | + &["txt"] |
| 161 | + } |
| 162 | + } |
| 163 | + |
| 164 | + #[derive(Default)] |
| 165 | + pub struct TextSaver; |
| 166 | + |
| 167 | + impl AssetSaver for TextSaver { |
| 168 | + type Asset = TextAsset; |
| 169 | + type Settings = (); |
| 170 | + type OutputLoader = TextLoader; |
| 171 | + type Error = std::io::Error; |
| 172 | + |
| 173 | + async fn save<'a>( |
| 174 | + &'a self, |
| 175 | + writer: &'a mut Writer, |
| 176 | + asset: SavedAsset<'a, Self::Asset>, |
| 177 | + _settings: &'a Self::Settings, |
| 178 | + ) -> Result<(), Self::Error> { |
| 179 | + writer.write_all(asset.0.as_bytes()).await?; |
| 180 | + Ok(()) |
| 181 | + } |
| 182 | + } |
| 183 | +} |
0 commit comments