From f8260320babb7e3270609774ff42192ae8fad981 Mon Sep 17 00:00:00 2001 From: Niklas Eicker Date: Sat, 21 Oct 2023 03:32:45 +0200 Subject: [PATCH 1/4] Not blocking load_untyped using a wrapper asset --- crates/bevy_asset/src/assets.rs | 14 ++++++++++-- crates/bevy_asset/src/lib.rs | 1 + crates/bevy_asset/src/processor/mod.rs | 2 +- crates/bevy_asset/src/server/mod.rs | 31 +++++++++++++++++++++++++- 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index a01a93c4dd71d..23d746f35376a 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -1,9 +1,10 @@ -use crate::{Asset, AssetEvent, AssetHandleProvider, AssetId, AssetServer, Handle}; +use crate as bevy_asset; +use crate::{Asset, AssetEvent, AssetHandleProvider, AssetId, AssetServer, Handle, UntypedHandle}; use bevy_ecs::{ prelude::EventWriter, system::{Res, ResMut, Resource}, }; -use bevy_reflect::{Reflect, Uuid}; +use bevy_reflect::{Reflect, TypePath, Uuid}; use bevy_utils::HashMap; use crossbeam_channel::{Receiver, Sender}; use serde::{Deserialize, Serialize}; @@ -74,6 +75,15 @@ impl AssetIndexAllocator { } } +/// A "loaded asset" containing the untyped handle for an asset stored in a given [`AssetPath`]. +/// +/// [`AssetPath`]: crate::AssetPath +#[derive(Asset, TypePath)] +pub struct LoadedUntypedAsset { + #[dependency] + pub handle: UntypedHandle, +} + // PERF: do we actually need this to be an enum? Can we just use an "invalid" generation instead #[derive(Default)] enum Entry { diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 58a2a191108c4..9f9cd44c77b0d 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -177,6 +177,7 @@ impl Plugin for AssetPlugin { } app.insert_resource(embedded) .init_asset::() + .init_asset::() .init_asset::<()>() .configure_sets( UpdateAssets, diff --git a/crates/bevy_asset/src/processor/mod.rs b/crates/bevy_asset/src/processor/mod.rs index 4e5b2a878ab00..6e1dd0dd6a38d 100644 --- a/crates/bevy_asset/src/processor/mod.rs +++ b/crates/bevy_asset/src/processor/mod.rs @@ -660,7 +660,7 @@ impl AssetProcessor { source: &AssetSource, asset_path: &AssetPath<'static>, ) -> Result { - // TODO: The extension check was removed now tht AssetPath is the input. is that ok? + // TODO: The extension check was removed now that AssetPath is the input. is that ok? // TODO: check if already processing to protect against duplicate hot-reload events debug!("Processing {:?}", asset_path); let server = &self.server; diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index 160a3e4d3228c..efa70ae13a756 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -13,7 +13,7 @@ use crate::{ }, path::AssetPath, Asset, AssetEvent, AssetHandleProvider, AssetId, Assets, DeserializeMetaError, - ErasedLoadedAsset, Handle, UntypedAssetId, UntypedHandle, + ErasedLoadedAsset, Handle, LoadedUntypedAsset, UntypedAssetId, UntypedHandle, }; use bevy_ecs::prelude::*; use bevy_log::{error, info, warn}; @@ -288,6 +288,35 @@ impl AssetServer { self.load_internal(None, path, false, None).await } + #[must_use = "not using the returned strong handle may result in the unexpected release of the assets"] + pub fn load_untyped<'a>(&self, path: impl Into>) -> Handle { + let handle = { + let mut infos = self.data.infos.write(); + infos.create_loading_handle::() + }; + let id = handle.id().untyped(); + let path = path.into().into_owned(); + + let server = self.clone(); + IoTaskPool::get() + .spawn(async move { + match server.load_untyped_async(path).await { + Ok(handle) => server.send_asset_event(InternalAssetEvent::Loaded { + id, + loaded_asset: LoadedAsset::new_with_dependencies( + LoadedUntypedAsset { handle }, + None, + ) + .into(), + }), + Err(_) => server.send_asset_event(InternalAssetEvent::Failed { id }), + } + }) + .detach(); + + handle + } + /// Performs an async asset load. /// /// `input_handle` must only be [`Some`] if `should_load` was true when retrieving `input_handle`. This is an optimization to From 08f0601e6613be6e18a636184260532e49ca5351 Mon Sep 17 00:00:00 2001 From: Niklas Eicker Date: Sun, 22 Oct 2023 15:49:22 +0200 Subject: [PATCH 2/4] Reuse handles to LoadedUntypedAsset --- crates/bevy_asset/src/server/mod.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index efa70ae13a756..9b8a819d763fb 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -290,12 +290,20 @@ impl AssetServer { #[must_use = "not using the returned strong handle may result in the unexpected release of the assets"] pub fn load_untyped<'a>(&self, path: impl Into>) -> Handle { - let handle = { - let mut infos = self.data.infos.write(); - infos.create_loading_handle::() - }; - let id = handle.id().untyped(); let path = path.into().into_owned(); + let (handle, should_load) = self + .data + .infos + .write() + .get_or_create_path_handle::( + path.clone().with_label("untyped"), + HandleLoadingMode::Request, + None, + ); + if !should_load { + return handle; + } + let id = handle.id().untyped(); let server = self.clone(); IoTaskPool::get() From 5022c7aa04dff5cae8a6f1434721713d11a27adf Mon Sep 17 00:00:00 2001 From: Niklas Eicker Date: Wed, 25 Oct 2023 13:37:21 +0200 Subject: [PATCH 3/4] Document load_untyped --- crates/bevy_asset/src/server/mod.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index 9b8a819d763fb..76320fead3618 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -288,6 +288,28 @@ impl AssetServer { self.load_internal(None, path, false, None).await } + /// Load an asset without knowing it's type. The method returns a handle to a [`LoadedUntypedAsset`]. + /// + /// Once the [`LoadedUntypedAsset`] is loaded, an untyped handle for the requested path can be + /// retrieved from it. + /// + /// ``` + /// use bevy_asset::{Assets, Handle, LoadedUntypedAsset}; + /// use bevy_ecs::system::{Res, Resource}; + /// + /// #[derive(Resource)] + /// struct LoadingUntypedHandle(Handle); + /// + /// fn resolve_loaded_untyped_handle(loading_handle: Res, loaded_untyped_assets: Res>) { + /// if let Some(loaded_untyped_asset) = loaded_untyped_assets.get(&loading_handle.0) { + /// let handle = loaded_untyped_asset.handle.clone(); + /// // continue working with `handle` which points to the asset at the originally requested path + /// } + /// } + /// ``` + /// + /// This indirection enables a non blocking load of an untyped asset, since I/O is + /// required to figure out the asset type before a handle can be created. #[must_use = "not using the returned strong handle may result in the unexpected release of the assets"] pub fn load_untyped<'a>(&self, path: impl Into>) -> Handle { let path = path.into().into_owned(); From b4b70a0931c9cf87ae6a24fad0b3c5ff9cecc3a8 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Thu, 26 Oct 2023 14:41:31 -0700 Subject: [PATCH 4/4] Use Asset Sources instead of Labels to make the internal load_untyped path. --- crates/bevy_asset/src/server/mod.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index 76320fead3618..73e73c25c8f93 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -18,7 +18,7 @@ use crate::{ use bevy_ecs::prelude::*; use bevy_log::{error, info, warn}; use bevy_tasks::IoTaskPool; -use bevy_utils::{HashMap, HashSet}; +use bevy_utils::{CowArc, HashMap, HashSet}; use crossbeam_channel::{Receiver, Sender}; use futures_lite::StreamExt; use info::*; @@ -313,12 +313,18 @@ impl AssetServer { #[must_use = "not using the returned strong handle may result in the unexpected release of the assets"] pub fn load_untyped<'a>(&self, path: impl Into>) -> Handle { let path = path.into().into_owned(); + let untyped_source = AssetSourceId::Name(match path.source() { + AssetSourceId::Default => CowArc::Borrowed(UNTYPED_SOURCE_SUFFIX), + AssetSourceId::Name(source) => { + CowArc::Owned(format!("{source}--{UNTYPED_SOURCE_SUFFIX}").into()) + } + }); let (handle, should_load) = self .data .infos .write() .get_or_create_path_handle::( - path.clone().with_label("untyped"), + path.clone().with_source(untyped_source), HandleLoadingMode::Request, None, ); @@ -1041,3 +1047,7 @@ impl std::fmt::Debug for AssetServer { .finish() } } + +/// This is appended to asset sources when loading a [`LoadedUntypedAsset`]. This provides a unique +/// source for a given [`AssetPath`]. +const UNTYPED_SOURCE_SUFFIX: &str = "--untyped";