From fcac6d60b7e2c7a5faab81f6b1c1958454f37188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Tue, 16 Mar 2021 00:55:22 +0100 Subject: [PATCH 01/15] renaming channel to better express their purpose --- crates/bevy_asset/src/asset_server.rs | 2 +- crates/bevy_asset/src/loader.rs | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index 0d8f1fe9512c7..34b9f9539e089 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -507,7 +507,7 @@ impl AssetServer { .unwrap(); loop { - match channel.receiver.try_recv() { + match channel.from_asset_server.try_recv() { Ok(AssetLifecycleEvent::Create(result)) => { // update SourceInfo if this asset was loaded from an AssetPath if let HandleId::AssetPathId(id) = result.id { diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 7b665bf8befe1..d55db68c654eb 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -158,8 +158,8 @@ pub struct AssetResult { /// A channel to send and receive [`AssetResult`]s #[derive(Debug)] pub struct AssetLifecycleChannel { - pub sender: Sender>, - pub receiver: Receiver>, + pub to_system: Sender>, + pub from_asset_server: Receiver>, } pub enum AssetLifecycleEvent { @@ -176,7 +176,7 @@ impl_downcast!(AssetLifecycle); impl AssetLifecycle for AssetLifecycleChannel { fn create_asset(&self, id: HandleId, asset: Box, version: usize) { if let Ok(asset) = asset.downcast::() { - self.sender + self.to_system .send(AssetLifecycleEvent::Create(AssetResult { asset, id, @@ -192,14 +192,17 @@ impl AssetLifecycle for AssetLifecycleChannel { } fn free_asset(&self, id: HandleId) { - self.sender.send(AssetLifecycleEvent::Free(id)).unwrap(); + self.to_system.send(AssetLifecycleEvent::Free(id)).unwrap(); } } impl Default for AssetLifecycleChannel { fn default() -> Self { - let (sender, receiver) = crossbeam_channel::unbounded(); - AssetLifecycleChannel { sender, receiver } + let (to_system, from_asset_server) = crossbeam_channel::unbounded(); + AssetLifecycleChannel { + to_system, + from_asset_server, + } } } From 192fe8ee6236f1258adde3326d2d5ece7e96a495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Tue, 16 Mar 2021 01:09:13 +0100 Subject: [PATCH 02/15] can create a new asset from an existing one --- Cargo.toml | 4 ++ crates/bevy_asset/src/asset_server.rs | 60 ++++++++++++++++++++- crates/bevy_asset/src/loader.rs | 26 +++++++++ examples/README.md | 1 + examples/asset/asset_transformation.rs | 73 ++++++++++++++++++++++++++ 5 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 examples/asset/asset_transformation.rs diff --git a/Cargo.toml b/Cargo.toml index 95df03bd94e2a..82c0dd8bdfede 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -270,6 +270,10 @@ path = "examples/app/without_winit.rs" name = "asset_loading" path = "examples/asset/asset_loading.rs" +[[example]] +name = "asset_transformation" +path = "examples/asset/asset_transformation.rs" + [[example]] name = "custom_asset" path = "examples/asset/custom_asset.rs" diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index 34b9f9539e089..1f63cbfd76f6d 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -237,7 +237,38 @@ impl AssetServer { self.load_untyped(path).typed() } - async fn load_async( + /// Create a new asset from another one of the same type + pub fn create_new_from( + &self, + original_handle: Handle, + transform: F, + ) -> Handle + where + F: FnOnce(&T) -> T, + F: Send + 'static, + { + let new_handle = HandleId::random::(); + let ret = self.get_handle_untyped(new_handle).typed(); + + let asset_lifecycles = self.server.asset_lifecycles.read(); + if let Some(asset_lifecycle) = asset_lifecycles.get(&T::TYPE_UUID) { + asset_lifecycle.create_asset_from( + original_handle.into(), + new_handle, + Box::new(|asset| { + Box::new(transform( + Box::new(asset) + .downcast_ref::() + .expect("This can't happen as asset is of type T"), + )) + }), + ); + } + + ret + } + + async fn load_async<'a>( &self, asset_path: AssetPath<'_>, force: bool, @@ -506,6 +537,7 @@ impl AssetServer { .downcast_ref::>() .unwrap(); + let mut rerun = vec![]; loop { match channel.from_asset_server.try_recv() { Ok(AssetLifecycleEvent::Create(result)) => { @@ -536,12 +568,38 @@ impl AssetServer { } assets.remove(handle_id); } + Ok(AssetLifecycleEvent::CreateNewFrom { + from, + to, + transform, + }) => { + if let Some(original) = assets.get(from) { + // `transform` can fail if it's result is not the expected type + // this should happen as public API to reach this point is stronly typed + // traits are only used in flight for communication + if let Ok(new) = transform(original) { + let _ = assets.set(to, *new); + } else { + warn!("Error converting an asset to it's type, please open an issue in Bevy GitHub repository"); + } + } else { + rerun.push(AssetLifecycleEvent::CreateNewFrom { + from, + to, + transform, + }); + } + } Err(TryRecvError::Empty) => { break; } Err(TryRecvError::Disconnected) => panic!("AssetChannel disconnected."), } } + + for message in rerun.into_iter() { + channel.to_system.send(message).unwrap(); + } } } diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index d55db68c654eb..5c5984f4e0046 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -164,11 +164,22 @@ pub struct AssetLifecycleChannel { pub enum AssetLifecycleEvent { Create(AssetResult), + CreateNewFrom { + from: HandleId, + to: HandleId, + transform: Box Result, ()>) + Send>, + }, Free(HandleId), } pub trait AssetLifecycle: Downcast + Send + Sync + 'static { fn create_asset(&self, id: HandleId, asset: Box, version: usize); + fn create_asset_from( + &self, + from: HandleId, + to: HandleId, + transform: Box Box) + Send>, + ); fn free_asset(&self, id: HandleId); } impl_downcast!(AssetLifecycle); @@ -191,6 +202,21 @@ impl AssetLifecycle for AssetLifecycleChannel { } } + fn create_asset_from( + &self, + from: HandleId, + to: HandleId, + transform: Box Box) + Send>, + ) { + self.to_system + .send(AssetLifecycleEvent::CreateNewFrom { + from, + to, + transform: Box::new(|asset: &T| transform(asset).downcast::().map_err(|_| ())), + }) + .unwrap(); + } + fn free_asset(&self, id: HandleId) { self.to_system.send(AssetLifecycleEvent::Free(id)).unwrap(); } diff --git a/examples/README.md b/examples/README.md index 8444192369692..2e9113fd3c20b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -136,6 +136,7 @@ Example | File | Description Example | File | Description --- | --- | --- `asset_loading` | [`asset/asset_loading.rs`](./asset/asset_loading.rs) | Demonstrates various methods to load assets +`asset_transformation` | [`asset/asset_transformation.rs`](./asset/asset_transformation.rs) | Create new assets from an existing one, with some transformation `custom_asset` | [`asset/custom_asset.rs`](./asset/custom_asset.rs) | Implements a custom asset loader `custom_asset_io` | [`asset/custom_asset_io.rs`](./asset/custom_asset_io.rs) | Implements a custom asset io loader `hot_asset_reloading` | [`asset/hot_asset_reloading.rs`](./asset/hot_asset_reloading.rs) | Demonstrates automatic reloading of assets when modified on disk diff --git a/examples/asset/asset_transformation.rs b/examples/asset/asset_transformation.rs new file mode 100644 index 0000000000000..158d61e827633 --- /dev/null +++ b/examples/asset/asset_transformation.rs @@ -0,0 +1,73 @@ +use bevy::prelude::*; + +fn main() { + App::build() + .add_plugins(DefaultPlugins) + .add_startup_system(setup.system()) + .run(); +} + +fn setup( + mut commands: Commands, + asset_server: Res, + mut materials: ResMut>, +) { + let texture_handle = asset_server.load("branding/icon.png"); + // new texture with one pixel every three removed + let texture_handle_1 = + asset_server.create_new_from(texture_handle.clone(), |texture: &Texture| { + let new_data = texture + .data + .iter() + .enumerate() + .map(|(i, v)| { + if i / texture.format.pixel_size() % 3 == 0 { + 0 + } else { + *v + } + }) + .collect(); + Texture { + data: new_data, + ..*texture + } + }); + // new texture with one pixel every two removed + let texture_handle_2 = + asset_server.create_new_from(texture_handle.clone(), |texture: &Texture| { + let new_data = texture + .data + .iter() + .enumerate() + .map(|(i, v)| { + if i / texture.format.pixel_size() % 2 == 0 { + 0 + } else { + *v + } + }) + .collect(); + Texture { + data: new_data, + ..*texture + } + }); + + commands + .spawn(OrthographicCameraBundle::new_2d()) + .spawn(SpriteBundle { + material: materials.add(texture_handle.into()), + transform: Transform::from_xyz(-300.0, 0.0, 0.0), + ..Default::default() + }) + .spawn(SpriteBundle { + material: materials.add(texture_handle_1.into()), + ..Default::default() + }) + .spawn(SpriteBundle { + material: materials.add(texture_handle_2.into()), + transform: Transform::from_xyz(300.0, 0.0, 0.0), + ..Default::default() + }); +} From 827db05693c1dcd3922a18037f782d9fa49f2d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Tue, 16 Mar 2021 01:29:42 +0100 Subject: [PATCH 03/15] typo and add comments --- crates/bevy_asset/src/asset_server.rs | 4 ++-- crates/bevy_asset/src/loader.rs | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index 1f63cbfd76f6d..84b9219b7d7be 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -574,8 +574,8 @@ impl AssetServer { transform, }) => { if let Some(original) = assets.get(from) { - // `transform` can fail if it's result is not the expected type - // this should happen as public API to reach this point is stronly typed + // `transform` can fail if its result is not the expected type + // this should not happen as public API to reach this point is stronly typed // traits are only used in flight for communication if let Ok(new) = transform(original) { let _ = assets.set(to, *new); diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 5c5984f4e0046..406b58af91a6a 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -212,7 +212,14 @@ impl AssetLifecycle for AssetLifecycleChannel { .send(AssetLifecycleEvent::CreateNewFrom { from, to, - transform: Box::new(|asset: &T| transform(asset).downcast::().map_err(|_| ())), + transform: Box::new(|asset: &T| { + transform(asset) + .downcast::() + // `downcast` can fail if `transform` result is not the expected type + // this should not happen as public API to reach this point is stronly typed + // traits are only used in flight for communication + .map_err(|_| ()) + }), }) .unwrap(); } From 21c2c14d39c072b43ff7ac2e66db17b5eab4d1c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Tue, 16 Mar 2021 02:25:04 +0100 Subject: [PATCH 04/15] typo --- crates/bevy_asset/src/asset_server.rs | 2 +- crates/bevy_asset/src/loader.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index 84b9219b7d7be..29088d06b87ac 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -575,7 +575,7 @@ impl AssetServer { }) => { if let Some(original) = assets.get(from) { // `transform` can fail if its result is not the expected type - // this should not happen as public API to reach this point is stronly typed + // this should not happen as public API to reach this point is strongly typed // traits are only used in flight for communication if let Ok(new) = transform(original) { let _ = assets.set(to, *new); diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 406b58af91a6a..908dbf93eae78 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -216,7 +216,7 @@ impl AssetLifecycle for AssetLifecycleChannel { transform(asset) .downcast::() // `downcast` can fail if `transform` result is not the expected type - // this should not happen as public API to reach this point is stronly typed + // this should not happen as public API to reach this point is strongly typed // traits are only used in flight for communication .map_err(|_| ()) }), From 9a4653505fa9bba48f8c55a11f551018a7fd678a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Tue, 16 Mar 2021 02:25:46 +0100 Subject: [PATCH 05/15] remove a box --- crates/bevy_asset/src/asset_server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index 29088d06b87ac..d265a8558e5ef 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -257,7 +257,7 @@ impl AssetServer { new_handle, Box::new(|asset| { Box::new(transform( - Box::new(asset) + asset .downcast_ref::() .expect("This can't happen as asset is of type T"), )) From 67e606b19aef3e62e55afb31478718aa815aee12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Tue, 16 Mar 2021 02:29:07 +0100 Subject: [PATCH 06/15] add type for easier reading --- crates/bevy_asset/src/asset_server.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index d265a8558e5ef..d855b7729b659 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -1,8 +1,8 @@ use crate::{ path::{AssetPath, AssetPathId, SourcePathId}, - Asset, AssetIo, AssetIoError, AssetLifecycle, AssetLifecycleChannel, AssetLifecycleEvent, - AssetLoader, Assets, Handle, HandleId, HandleUntyped, LabelId, LoadContext, LoadState, - RefChange, RefChangeChannel, SourceInfo, SourceMeta, + Asset, AssetDynamic, AssetIo, AssetIoError, AssetLifecycle, AssetLifecycleChannel, + AssetLifecycleEvent, AssetLoader, Assets, Handle, HandleId, HandleUntyped, LabelId, + LoadContext, LoadState, RefChange, RefChangeChannel, SourceInfo, SourceMeta, }; use anyhow::Result; use bevy_ecs::system::{Res, ResMut}; @@ -255,7 +255,7 @@ impl AssetServer { asset_lifecycle.create_asset_from( original_handle.into(), new_handle, - Box::new(|asset| { + Box::new(|asset: &dyn AssetDynamic| { Box::new(transform( asset .downcast_ref::() From 897e1b7aefdc8d592fff465983cf3f1c3a6ef184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Tue, 16 Mar 2021 04:15:14 +0100 Subject: [PATCH 07/15] work between different types --- crates/bevy_asset/src/asset_server.rs | 34 +++++++++++++-------------- crates/bevy_asset/src/loader.rs | 17 ++++++-------- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index d855b7729b659..c1974d9aa638c 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -237,29 +237,30 @@ impl AssetServer { self.load_untyped(path).typed() } - /// Create a new asset from another one of the same type - pub fn create_new_from( + /// Create a new asset from another one + pub fn create_new_from( &self, - original_handle: Handle, + original_handle: Handle, transform: F, - ) -> Handle + ) -> Handle where - F: FnOnce(&T) -> T, + F: FnOnce(&FROM) -> TO, F: Send + 'static, { - let new_handle = HandleId::random::(); + let new_handle = HandleId::random::(); let ret = self.get_handle_untyped(new_handle).typed(); let asset_lifecycles = self.server.asset_lifecycles.read(); - if let Some(asset_lifecycle) = asset_lifecycles.get(&T::TYPE_UUID) { + if let Some(asset_lifecycle) = asset_lifecycles.get(&FROM::TYPE_UUID) { asset_lifecycle.create_asset_from( original_handle.into(), new_handle, + TO::TYPE_UUID, Box::new(|asset: &dyn AssetDynamic| { Box::new(transform( asset - .downcast_ref::() - .expect("This can't happen as asset is of type T"), + .downcast_ref::() + .expect("Error converting an asset to it's type, please open an issue in Bevy GitHub repository"), )) }), ); @@ -571,21 +572,20 @@ impl AssetServer { Ok(AssetLifecycleEvent::CreateNewFrom { from, to, + to_uuid, transform, }) => { if let Some(original) = assets.get(from) { - // `transform` can fail if its result is not the expected type - // this should not happen as public API to reach this point is strongly typed - // traits are only used in flight for communication - if let Ok(new) = transform(original) { - let _ = assets.set(to, *new); - } else { - warn!("Error converting an asset to it's type, please open an issue in Bevy GitHub repository"); - } + let new = transform(original); + asset_lifecycles + .get(&to_uuid) + .unwrap() + .create_asset(to, new, 0); } else { rerun.push(AssetLifecycleEvent::CreateNewFrom { from, to, + to_uuid, transform, }); } diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 908dbf93eae78..36f9bb2a03ab4 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -4,7 +4,7 @@ use crate::{ }; use anyhow::Result; use bevy_ecs::system::{Res, ResMut}; -use bevy_reflect::{TypeUuid, TypeUuidDynamic}; +use bevy_reflect::{TypeUuid, TypeUuidDynamic, Uuid}; use bevy_tasks::TaskPool; use bevy_utils::{BoxedFuture, HashMap}; use crossbeam_channel::{Receiver, Sender}; @@ -167,7 +167,8 @@ pub enum AssetLifecycleEvent { CreateNewFrom { from: HandleId, to: HandleId, - transform: Box Result, ()>) + Send>, + to_uuid: Uuid, + transform: Box Box) + Send>, }, Free(HandleId), } @@ -178,6 +179,7 @@ pub trait AssetLifecycle: Downcast + Send + Sync + 'static { &self, from: HandleId, to: HandleId, + to_uuid: Uuid, transform: Box Box) + Send>, ); fn free_asset(&self, id: HandleId); @@ -206,20 +208,15 @@ impl AssetLifecycle for AssetLifecycleChannel { &self, from: HandleId, to: HandleId, + to_uuid: Uuid, transform: Box Box) + Send>, ) { self.to_system .send(AssetLifecycleEvent::CreateNewFrom { from, to, - transform: Box::new(|asset: &T| { - transform(asset) - .downcast::() - // `downcast` can fail if `transform` result is not the expected type - // this should not happen as public API to reach this point is strongly typed - // traits are only used in flight for communication - .map_err(|_| ()) - }), + to_uuid, + transform: Box::new(|asset: &T| transform(asset)), }) .unwrap(); } From 9b74dba68dc1f09cf262a10bb34e6f0913cd1622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Tue, 16 Mar 2021 04:22:24 +0100 Subject: [PATCH 08/15] small renaming --- crates/bevy_asset/src/asset_server.rs | 6 +++--- crates/bevy_asset/src/loader.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index c1974d9aa638c..4c434b9be3bc9 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -238,7 +238,7 @@ impl AssetServer { } /// Create a new asset from another one - pub fn create_new_from( + pub fn create_from( &self, original_handle: Handle, transform: F, @@ -569,7 +569,7 @@ impl AssetServer { } assets.remove(handle_id); } - Ok(AssetLifecycleEvent::CreateNewFrom { + Ok(AssetLifecycleEvent::CreateFrom { from, to, to_uuid, @@ -582,7 +582,7 @@ impl AssetServer { .unwrap() .create_asset(to, new, 0); } else { - rerun.push(AssetLifecycleEvent::CreateNewFrom { + rerun.push(AssetLifecycleEvent::CreateFrom { from, to, to_uuid, diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 36f9bb2a03ab4..3a589daab3334 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -164,7 +164,7 @@ pub struct AssetLifecycleChannel { pub enum AssetLifecycleEvent { Create(AssetResult), - CreateNewFrom { + CreateFrom { from: HandleId, to: HandleId, to_uuid: Uuid, @@ -212,7 +212,7 @@ impl AssetLifecycle for AssetLifecycleChannel { transform: Box Box) + Send>, ) { self.to_system - .send(AssetLifecycleEvent::CreateNewFrom { + .send(AssetLifecycleEvent::CreateFrom { from, to, to_uuid, From 3ff89d83a67e8645b0f6737259b3651daae2e797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Tue, 16 Mar 2021 13:00:29 +0100 Subject: [PATCH 09/15] improve usability, error handling and comments --- crates/bevy_asset/src/asset_server.rs | 49 ++++++++++++++----- crates/bevy_asset/src/loader.rs | 18 ++++--- examples/asset/asset_transformation.rs | 67 +++++++++++--------------- 3 files changed, 78 insertions(+), 56 deletions(-) diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index 4c434b9be3bc9..e9082b23fa458 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -6,7 +6,7 @@ use crate::{ }; use anyhow::Result; use bevy_ecs::system::{Res, ResMut}; -use bevy_log::warn; +use bevy_log::{debug, warn}; use bevy_tasks::TaskPool; use bevy_utils::{HashMap, Uuid}; use crossbeam_channel::TryRecvError; @@ -244,7 +244,7 @@ impl AssetServer { transform: F, ) -> Handle where - F: FnOnce(&FROM) -> TO, + F: FnOnce(&FROM) -> Option, F: Send + 'static, { let new_handle = HandleId::random::(); @@ -257,11 +257,17 @@ impl AssetServer { new_handle, TO::TYPE_UUID, Box::new(|asset: &dyn AssetDynamic| { - Box::new(transform( + if let Some(transformed) = transform( asset .downcast_ref::() + // this downcast can't fail as we know the actual types here .expect("Error converting an asset to it's type, please open an issue in Bevy GitHub repository"), - )) + ) { + Some(Box::new(transformed)) + } else { + warn!("Error creating a new asset from {} to {}", std::any::type_name::(), std::any::type_name::()); + None + } }), ); } @@ -572,20 +578,41 @@ impl AssetServer { Ok(AssetLifecycleEvent::CreateFrom { from, to, - to_uuid, + to_type_uuid, transform, }) => { if let Some(original) = assets.get(from) { - let new = transform(original); - asset_lifecycles - .get(&to_uuid) - .unwrap() - .create_asset(to, new, 0); + if let Some(new) = transform(original) { + if T::TYPE_UUID == to_type_uuid { + // New asset is of same type as original, add it to the `assets` + if let Ok(new_asset) = new.downcast::() { + let _ = assets.set(to, *new_asset); + } else { + warn!( + "Failed to downcast transformed asset to {}.", + std::any::type_name::() + ); + } + } else { + // Converting to another asset type, use its `asset_lifecycle` to create it + asset_lifecycles + .get(&to_type_uuid) + .unwrap() + .create_asset(to, new, 0); + } + } else { + // Log as debug here, it should have already been logged as warn + debug!( + "Failed to transform asset from {}.", + std::any::type_name::() + ); + } } else { + // Asset is not yet ready, retry next frame rerun.push(AssetLifecycleEvent::CreateFrom { from, to, - to_uuid, + to_type_uuid, transform, }); } diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 3a589daab3334..0af11e0d10d95 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -4,6 +4,7 @@ use crate::{ }; use anyhow::Result; use bevy_ecs::system::{Res, ResMut}; +use bevy_log::warn; use bevy_reflect::{TypeUuid, TypeUuidDynamic, Uuid}; use bevy_tasks::TaskPool; use bevy_utils::{BoxedFuture, HashMap}; @@ -167,8 +168,8 @@ pub enum AssetLifecycleEvent { CreateFrom { from: HandleId, to: HandleId, - to_uuid: Uuid, - transform: Box Box) + Send>, + to_type_uuid: Uuid, + transform: Box Option>) + Send>, }, Free(HandleId), } @@ -180,7 +181,7 @@ pub trait AssetLifecycle: Downcast + Send + Sync + 'static { from: HandleId, to: HandleId, to_uuid: Uuid, - transform: Box Box) + Send>, + transform: Box Option>) + Send>, ); fn free_asset(&self, id: HandleId); } @@ -197,25 +198,28 @@ impl AssetLifecycle for AssetLifecycleChannel { })) .unwrap() } else { - panic!( + warn!( "Failed to downcast asset to {}.", std::any::type_name::() ); } } + /// Convert an asset from type `T` to type with `T_TO::TYPE_UUID == to_uuid`. + /// It is the responsibility of the caller to ensure that input/output of `transform` + /// matches those types fn create_asset_from( &self, from: HandleId, to: HandleId, - to_uuid: Uuid, - transform: Box Box) + Send>, + to_type_uuid: Uuid, + transform: Box Option>) + Send>, ) { self.to_system .send(AssetLifecycleEvent::CreateFrom { from, to, - to_uuid, + to_type_uuid, transform: Box::new(|asset: &T| transform(asset)), }) .unwrap(); diff --git a/examples/asset/asset_transformation.rs b/examples/asset/asset_transformation.rs index 158d61e827633..8200388e519f6 100644 --- a/examples/asset/asset_transformation.rs +++ b/examples/asset/asset_transformation.rs @@ -7,52 +7,43 @@ fn main() { .run(); } +fn filter_pixels(filter: usize, texture: &Texture) -> Vec { + texture + .data + .iter() + .enumerate() + .map(|(i, v)| { + if i / texture.format.pixel_size() % filter == 0 { + 0 + } else { + *v + } + }) + .collect() +} + fn setup( mut commands: Commands, asset_server: Res, mut materials: ResMut>, ) { let texture_handle = asset_server.load("branding/icon.png"); + // new texture with one pixel every three removed - let texture_handle_1 = - asset_server.create_new_from(texture_handle.clone(), |texture: &Texture| { - let new_data = texture - .data - .iter() - .enumerate() - .map(|(i, v)| { - if i / texture.format.pixel_size() % 3 == 0 { - 0 - } else { - *v - } - }) - .collect(); - Texture { - data: new_data, - ..*texture - } - }); + let texture_handle_1 = asset_server.create_from(texture_handle.clone(), |texture: &Texture| { + Some(Texture { + data: filter_pixels(3, texture), + ..*texture + }) + }); + // new texture with one pixel every two removed - let texture_handle_2 = - asset_server.create_new_from(texture_handle.clone(), |texture: &Texture| { - let new_data = texture - .data - .iter() - .enumerate() - .map(|(i, v)| { - if i / texture.format.pixel_size() % 2 == 0 { - 0 - } else { - *v - } - }) - .collect(); - Texture { - data: new_data, - ..*texture - } - }); + let texture_handle_2 = asset_server.create_from(texture_handle.clone(), |texture: &Texture| { + Some(Texture { + data: filter_pixels(2, texture), + ..*texture + }) + }); commands .spawn(OrthographicCameraBundle::new_2d()) From 4fdf02c9e9bc995bea779a24916404b066e3c618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Tue, 16 Mar 2021 13:17:37 +0100 Subject: [PATCH 10/15] renaming --- crates/bevy_asset/src/asset_server.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index e9082b23fa458..5547c7a186bf9 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -240,7 +240,7 @@ impl AssetServer { /// Create a new asset from another one pub fn create_from( &self, - original_handle: Handle, + from_handle: Handle, transform: F, ) -> Handle where @@ -248,12 +248,15 @@ impl AssetServer { F: Send + 'static, { let new_handle = HandleId::random::(); - let ret = self.get_handle_untyped(new_handle).typed(); + let to_handle = Handle::strong( + new_handle, + self.server.asset_ref_counter.channel.sender.clone(), + ); let asset_lifecycles = self.server.asset_lifecycles.read(); if let Some(asset_lifecycle) = asset_lifecycles.get(&FROM::TYPE_UUID) { asset_lifecycle.create_asset_from( - original_handle.into(), + from_handle.into(), new_handle, TO::TYPE_UUID, Box::new(|asset: &dyn AssetDynamic| { @@ -272,7 +275,7 @@ impl AssetServer { ); } - ret + to_handle } async fn load_async<'a>( From 3fe8c1649affd80657aa1c2d6222b18d32f96eac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Tue, 16 Mar 2021 21:46:50 +0100 Subject: [PATCH 11/15] improve comments/logs --- crates/bevy_asset/src/asset_server.rs | 8 ++++++-- examples/asset/asset_transformation.rs | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index 5547c7a186bf9..ac71dfac1b990 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -264,11 +264,15 @@ impl AssetServer { asset .downcast_ref::() // this downcast can't fail as we know the actual types here - .expect("Error converting an asset to it's type, please open an issue in Bevy GitHub repository"), + .expect("Error converting an asset to its type, please open an issue in Bevy GitHub repository"), ) { Some(Box::new(transformed)) } else { - warn!("Error creating a new asset from {} to {}", std::any::type_name::(), std::any::type_name::()); + warn!( + "Error creating a new asset from {}, attempting to convert to {}", + std::any::type_name::(), + std::any::type_name::() + ); None } }), diff --git a/examples/asset/asset_transformation.rs b/examples/asset/asset_transformation.rs index 8200388e519f6..1063376206c6e 100644 --- a/examples/asset/asset_transformation.rs +++ b/examples/asset/asset_transformation.rs @@ -29,7 +29,7 @@ fn setup( ) { let texture_handle = asset_server.load("branding/icon.png"); - // new texture with one pixel every three removed + // new texture with every third pixel removed let texture_handle_1 = asset_server.create_from(texture_handle.clone(), |texture: &Texture| { Some(Texture { data: filter_pixels(3, texture), @@ -37,7 +37,7 @@ fn setup( }) }); - // new texture with one pixel every two removed + // new texture with every second pixel removed let texture_handle_2 = asset_server.create_from(texture_handle.clone(), |texture: &Texture| { Some(Texture { data: filter_pixels(2, texture), From 481298df7af6ba30cf9133c5c411861e96e361a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Fri, 19 Mar 2021 12:16:35 +0100 Subject: [PATCH 12/15] type parameter names --- crates/bevy_asset/src/asset_server.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index ac71dfac1b990..9df50de7d9f1f 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -238,31 +238,31 @@ impl AssetServer { } /// Create a new asset from another one - pub fn create_from( + pub fn create_from( &self, - from_handle: Handle, - transform: F, - ) -> Handle + from_handle: Handle, + transform: Func, + ) -> Handle where - F: FnOnce(&FROM) -> Option, - F: Send + 'static, + Func: FnOnce(&From) -> Option, + Func: Send + 'static, { - let new_handle = HandleId::random::(); + let new_handle = HandleId::random::(); let to_handle = Handle::strong( new_handle, self.server.asset_ref_counter.channel.sender.clone(), ); let asset_lifecycles = self.server.asset_lifecycles.read(); - if let Some(asset_lifecycle) = asset_lifecycles.get(&FROM::TYPE_UUID) { + if let Some(asset_lifecycle) = asset_lifecycles.get(&From::TYPE_UUID) { asset_lifecycle.create_asset_from( from_handle.into(), new_handle, - TO::TYPE_UUID, + To::TYPE_UUID, Box::new(|asset: &dyn AssetDynamic| { if let Some(transformed) = transform( asset - .downcast_ref::() + .downcast_ref::() // this downcast can't fail as we know the actual types here .expect("Error converting an asset to its type, please open an issue in Bevy GitHub repository"), ) { @@ -270,8 +270,8 @@ impl AssetServer { } else { warn!( "Error creating a new asset from {}, attempting to convert to {}", - std::any::type_name::(), - std::any::type_name::() + std::any::type_name::(), + std::any::type_name::() ); None } From 41fb26f9d21b04447e4ea40f4a16d51bd9fb7885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Fri, 19 Mar 2021 12:24:15 +0100 Subject: [PATCH 13/15] fix doc comment --- crates/bevy_asset/src/loader.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 0af11e0d10d95..f8af5024265c8 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -205,7 +205,7 @@ impl AssetLifecycle for AssetLifecycleChannel { } } - /// Convert an asset from type `T` to type with `T_TO::TYPE_UUID == to_uuid`. + /// Convert an asset from type `T` to type with `To::TYPE_UUID == to_uuid`. /// It is the responsibility of the caller to ensure that input/output of `transform` /// matches those types fn create_asset_from( From 431910131d39f69bffcd346c41703275c9ca0e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Tue, 23 Mar 2021 19:51:20 +0100 Subject: [PATCH 14/15] update for new commands api --- examples/asset/asset_transformation.rs | 32 +++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/examples/asset/asset_transformation.rs b/examples/asset/asset_transformation.rs index 1063376206c6e..1df2ae3ab2d64 100644 --- a/examples/asset/asset_transformation.rs +++ b/examples/asset/asset_transformation.rs @@ -45,20 +45,20 @@ fn setup( }) }); - commands - .spawn(OrthographicCameraBundle::new_2d()) - .spawn(SpriteBundle { - material: materials.add(texture_handle.into()), - transform: Transform::from_xyz(-300.0, 0.0, 0.0), - ..Default::default() - }) - .spawn(SpriteBundle { - material: materials.add(texture_handle_1.into()), - ..Default::default() - }) - .spawn(SpriteBundle { - material: materials.add(texture_handle_2.into()), - transform: Transform::from_xyz(300.0, 0.0, 0.0), - ..Default::default() - }); + commands.spawn_bundle(OrthographicCameraBundle::new_2d()); + + commands.spawn_bundle(SpriteBundle { + material: materials.add(texture_handle.into()), + transform: Transform::from_xyz(-300.0, 0.0, 0.0), + ..Default::default() + }); + commands.spawn_bundle(SpriteBundle { + material: materials.add(texture_handle_1.into()), + ..Default::default() + }); + commands.spawn_bundle(SpriteBundle { + material: materials.add(texture_handle_2.into()), + transform: Transform::from_xyz(300.0, 0.0, 0.0), + ..Default::default() + }); } From d847c07733dcc19371e240f96259570ca19b464f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Fri, 4 Feb 2022 20:20:36 +0100 Subject: [PATCH 15/15] update example for new renderer --- examples/asset/asset_transformation.rs | 34 ++++++++++++-------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/examples/asset/asset_transformation.rs b/examples/asset/asset_transformation.rs index 1df2ae3ab2d64..d85db22a28139 100644 --- a/examples/asset/asset_transformation.rs +++ b/examples/asset/asset_transformation.rs @@ -1,19 +1,19 @@ -use bevy::prelude::*; +use bevy::{prelude::*, render::texture::TextureFormatPixelInfo}; fn main() { - App::build() + App::new() .add_plugins(DefaultPlugins) .add_startup_system(setup.system()) .run(); } -fn filter_pixels(filter: usize, texture: &Texture) -> Vec { - texture +fn filter_pixels(filter: usize, image: &Image) -> Vec { + image .data .iter() .enumerate() .map(|(i, v)| { - if i / texture.format.pixel_size() % filter == 0 { + if i / image.texture_descriptor.format.pixel_size() % filter == 0 { 0 } else { *v @@ -22,42 +22,38 @@ fn filter_pixels(filter: usize, texture: &Texture) -> Vec { .collect() } -fn setup( - mut commands: Commands, - asset_server: Res, - mut materials: ResMut>, -) { +fn setup(mut commands: Commands, asset_server: Res) { let texture_handle = asset_server.load("branding/icon.png"); // new texture with every third pixel removed - let texture_handle_1 = asset_server.create_from(texture_handle.clone(), |texture: &Texture| { - Some(Texture { + let texture_handle_1 = asset_server.create_from(texture_handle.clone(), |texture: &Image| { + Some(Image { data: filter_pixels(3, texture), - ..*texture + ..texture.clone() }) }); // new texture with every second pixel removed - let texture_handle_2 = asset_server.create_from(texture_handle.clone(), |texture: &Texture| { - Some(Texture { + let texture_handle_2 = asset_server.create_from(texture_handle.clone(), |texture: &Image| { + Some(Image { data: filter_pixels(2, texture), - ..*texture + ..texture.clone() }) }); commands.spawn_bundle(OrthographicCameraBundle::new_2d()); commands.spawn_bundle(SpriteBundle { - material: materials.add(texture_handle.into()), + texture: texture_handle, transform: Transform::from_xyz(-300.0, 0.0, 0.0), ..Default::default() }); commands.spawn_bundle(SpriteBundle { - material: materials.add(texture_handle_1.into()), + texture: texture_handle_1, ..Default::default() }); commands.spawn_bundle(SpriteBundle { - material: materials.add(texture_handle_2.into()), + texture: texture_handle_2, transform: Transform::from_xyz(300.0, 0.0, 0.0), ..Default::default() });