mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
Added Support for Extension-less Assets (#10153)
# Objective - Addresses **Support processing and loading files without extensions** from #9714 - Addresses **More runtime loading configuration** from #9714 - Fixes #367 - Fixes #10703 ## Solution `AssetServer::load::<A>` and `AssetServer::load_with_settings::<A>` can now use the `Asset` type parameter `A` to select a registered `AssetLoader` without inspecting the provided `AssetPath`. This change cascades onto `LoadContext::load` and `LoadContext::load_with_settings`. This allows the loading of assets which have incorrect or ambiguous file extensions. ```rust // Allow the type to be inferred by context let handle = asset_server.load("data/asset_no_extension"); // Hint the type through the handle let handle: Handle<CustomAsset> = asset_server.load("data/asset_no_extension"); // Explicit through turbofish let handle = asset_server.load::<CustomAsset>("data/asset_no_extension"); ``` Since a single `AssetPath` no longer maps 1:1 with an `Asset`, I've also modified how assets are loaded to permit multiple asset types to be loaded from a single path. This allows for two different `AssetLoaders` (which return different types of assets) to both load a single path (if requested). ```rust // Uses GltfLoader let model = asset_server.load::<Gltf>("cube.gltf"); // Hypothetical Blob loader for data transmission (for example) let blob = asset_server.load::<Blob>("cube.gltf"); ``` As these changes are reflected in the `LoadContext` as well as the `AssetServer`, custom `AssetLoaders` can also take advantage of this behaviour to create more complex assets. --- ## Change Log - Updated `custom_asset` example to demonstrate extension-less assets. - Added `AssetServer::get_handles_untyped` and Added `AssetServer::get_path_ids` ## Notes As a part of that refactor, I chose to store `AssetLoader`s (within `AssetLoaders`) using a `HashMap<TypeId, ...>` instead of a `Vec<...>`. My reasoning for this was I needed to add a relationship between `Asset` `TypeId`s and the `AssetLoader`, so instead of having a `Vec` and a `HashMap`, I combined the two, removing the `usize` index from the adjacent maps. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
parent
16d28ccb91
commit
afa7b5cba5
6 changed files with 336 additions and 57 deletions
3
assets/data/asset_no_extension
Normal file
3
assets/data/asset_no_extension
Normal file
|
@ -0,0 +1,3 @@
|
|||
CustomAsset (
|
||||
value: 13
|
||||
)
|
|
@ -267,10 +267,7 @@ impl AsyncRead for VecReader {
|
|||
/// Appends `.meta` to the given path.
|
||||
pub(crate) fn get_meta_path(path: &Path) -> PathBuf {
|
||||
let mut meta_path = path.to_path_buf();
|
||||
let mut extension = path
|
||||
.extension()
|
||||
.unwrap_or_else(|| panic!("missing extension for asset path {path:?}"))
|
||||
.to_os_string();
|
||||
let mut extension = path.extension().unwrap_or_default().to_os_string();
|
||||
extension.push(".meta");
|
||||
meta_path.set_extension(extension);
|
||||
meta_path
|
||||
|
|
|
@ -37,8 +37,10 @@ pub trait AssetLoader: Send + Sync + 'static {
|
|||
load_context: &'a mut LoadContext,
|
||||
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>>;
|
||||
|
||||
/// Returns a list of extensions supported by this asset loader, without the preceding dot.
|
||||
fn extensions(&self) -> &[&str];
|
||||
/// Returns a list of extensions supported by this [`AssetLoader`], without the preceding dot.
|
||||
fn extensions(&self) -> &[&str] {
|
||||
&[]
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides type-erased access to an [`AssetLoader`].
|
||||
|
@ -396,7 +398,7 @@ impl<'a> LoadContext<'a> {
|
|||
/// See [`AssetPath`] for more on labeled assets.
|
||||
pub fn has_labeled_asset<'b>(&self, label: impl Into<CowArc<'b, str>>) -> bool {
|
||||
let path = self.asset_path.clone().with_label(label.into());
|
||||
self.asset_server.get_handle_untyped(&path).is_some()
|
||||
!self.asset_server.get_handles_untyped(&path).is_empty()
|
||||
}
|
||||
|
||||
/// "Finishes" this context by populating the final [`Asset`] value (and the erased [`AssetMeta`] value, if it exists).
|
||||
|
@ -546,7 +548,7 @@ impl<'a> LoadContext<'a> {
|
|||
let loaded_asset = {
|
||||
let (meta, loader, mut reader) = self
|
||||
.asset_server
|
||||
.get_meta_loader_and_reader(&path)
|
||||
.get_meta_loader_and_reader(&path, None)
|
||||
.await
|
||||
.map_err(to_error)?;
|
||||
self.asset_server
|
||||
|
|
|
@ -61,7 +61,7 @@ impl AssetInfo {
|
|||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct AssetInfos {
|
||||
path_to_id: HashMap<AssetPath<'static>, UntypedAssetId>,
|
||||
path_to_id: HashMap<AssetPath<'static>, HashMap<TypeId, UntypedAssetId>>,
|
||||
infos: HashMap<UntypedAssetId, AssetInfo>,
|
||||
/// If set to `true`, this informs [`AssetInfos`] to track data relevant to watching for changes (such as `load_dependants`)
|
||||
/// This should only be set at startup.
|
||||
|
@ -191,7 +191,20 @@ impl AssetInfos {
|
|||
loading_mode: HandleLoadingMode,
|
||||
meta_transform: Option<MetaTransform>,
|
||||
) -> Result<(UntypedHandle, bool), GetOrCreateHandleInternalError> {
|
||||
match self.path_to_id.entry(path.clone()) {
|
||||
let handles = self.path_to_id.entry(path.clone()).or_default();
|
||||
|
||||
let type_id = type_id
|
||||
.or_else(|| {
|
||||
// If a TypeId is not provided, we may be able to infer it if only a single entry exists
|
||||
if handles.len() == 1 {
|
||||
Some(*handles.keys().next().unwrap())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.ok_or(GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified)?;
|
||||
|
||||
match handles.entry(type_id) {
|
||||
Entry::Occupied(entry) => {
|
||||
let id = *entry.get();
|
||||
// if there is a path_to_id entry, info always exists
|
||||
|
@ -222,9 +235,6 @@ impl AssetInfos {
|
|||
// We must create a new strong handle for the existing id and ensure that the drop of the old
|
||||
// strong handle doesn't remove the asset from the Assets collection
|
||||
info.handle_drops_to_skip += 1;
|
||||
let type_id = type_id.ok_or(
|
||||
GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified,
|
||||
)?;
|
||||
let provider = self
|
||||
.handle_providers
|
||||
.get(&type_id)
|
||||
|
@ -241,8 +251,6 @@ impl AssetInfos {
|
|||
HandleLoadingMode::NotLoading => false,
|
||||
HandleLoadingMode::Request | HandleLoadingMode::Force => true,
|
||||
};
|
||||
let type_id = type_id
|
||||
.ok_or(GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified)?;
|
||||
let handle = Self::create_handle_internal(
|
||||
&mut self.infos,
|
||||
&self.handle_providers,
|
||||
|
@ -271,13 +279,52 @@ impl AssetInfos {
|
|||
self.infos.get_mut(&id)
|
||||
}
|
||||
|
||||
pub(crate) fn get_path_id(&self, path: &AssetPath) -> Option<UntypedAssetId> {
|
||||
self.path_to_id.get(path).copied()
|
||||
pub(crate) fn get_path_and_type_id_handle(
|
||||
&self,
|
||||
path: &AssetPath,
|
||||
type_id: TypeId,
|
||||
) -> Option<UntypedHandle> {
|
||||
let id = self.path_to_id.get(path)?.get(&type_id)?;
|
||||
self.get_id_handle(*id)
|
||||
}
|
||||
|
||||
pub(crate) fn get_path_handle(&self, path: &AssetPath) -> Option<UntypedHandle> {
|
||||
let id = *self.path_to_id.get(path)?;
|
||||
self.get_id_handle(id)
|
||||
pub(crate) fn get_path_ids<'a>(
|
||||
&'a self,
|
||||
path: &'a AssetPath<'a>,
|
||||
) -> impl Iterator<Item = UntypedAssetId> + 'a {
|
||||
/// Concrete type to allow returning an `impl Iterator` even if `self.path_to_id.get(&path)` is `None`
|
||||
enum HandlesByPathIterator<T> {
|
||||
None,
|
||||
Some(T),
|
||||
}
|
||||
|
||||
impl<T> Iterator for HandlesByPathIterator<T>
|
||||
where
|
||||
T: Iterator<Item = UntypedAssetId>,
|
||||
{
|
||||
type Item = UntypedAssetId;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
HandlesByPathIterator::None => None,
|
||||
HandlesByPathIterator::Some(iter) => iter.next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(type_id_to_id) = self.path_to_id.get(path) {
|
||||
HandlesByPathIterator::Some(type_id_to_id.values().copied())
|
||||
} else {
|
||||
HandlesByPathIterator::None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_path_handles<'a>(
|
||||
&'a self,
|
||||
path: &'a AssetPath<'a>,
|
||||
) -> impl Iterator<Item = UntypedHandle> + 'a {
|
||||
self.get_path_ids(path)
|
||||
.filter_map(|id| self.get_id_handle(id))
|
||||
}
|
||||
|
||||
pub(crate) fn get_id_handle(&self, id: UntypedAssetId) -> Option<UntypedHandle> {
|
||||
|
@ -289,12 +336,13 @@ impl AssetInfos {
|
|||
/// Returns `true` if the asset this path points to is still alive
|
||||
pub(crate) fn is_path_alive<'a>(&self, path: impl Into<AssetPath<'a>>) -> bool {
|
||||
let path = path.into();
|
||||
if let Some(id) = self.path_to_id.get(&path) {
|
||||
if let Some(info) = self.infos.get(id) {
|
||||
return info.weak_handle.strong_count() > 0;
|
||||
}
|
||||
}
|
||||
false
|
||||
|
||||
let result = self
|
||||
.get_path_ids(&path)
|
||||
.filter_map(|id| self.infos.get(&id))
|
||||
.any(|info| info.weak_handle.strong_count() > 0);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns `true` if the asset at this path should be reloaded
|
||||
|
@ -592,7 +640,7 @@ impl AssetInfos {
|
|||
|
||||
fn process_handle_drop_internal(
|
||||
infos: &mut HashMap<UntypedAssetId, AssetInfo>,
|
||||
path_to_id: &mut HashMap<AssetPath<'static>, UntypedAssetId>,
|
||||
path_to_id: &mut HashMap<AssetPath<'static>, HashMap<TypeId, UntypedAssetId>>,
|
||||
loader_dependants: &mut HashMap<AssetPath<'static>, HashSet<AssetPath<'static>>>,
|
||||
living_labeled_assets: &mut HashMap<AssetPath<'static>, HashSet<String>>,
|
||||
watching_for_changes: bool,
|
||||
|
@ -609,6 +657,8 @@ impl AssetInfos {
|
|||
return false;
|
||||
}
|
||||
|
||||
let type_id = entry.key().type_id();
|
||||
|
||||
let info = entry.remove();
|
||||
let Some(path) = &info.path else {
|
||||
return true;
|
||||
|
@ -622,7 +672,15 @@ impl AssetInfos {
|
|||
living_labeled_assets,
|
||||
);
|
||||
}
|
||||
path_to_id.remove(path);
|
||||
|
||||
if let Some(map) = path_to_id.get_mut(path) {
|
||||
map.remove(&type_id);
|
||||
|
||||
if map.is_empty() {
|
||||
path_to_id.remove(path);
|
||||
}
|
||||
};
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
|
|
|
@ -142,20 +142,22 @@ impl AssetServer {
|
|||
if let Some(index) = loaders.preregistered_loaders.remove(type_name) {
|
||||
(index, false)
|
||||
} else {
|
||||
(loaders.values.len(), true)
|
||||
(TypeId::of::<L::Asset>(), true)
|
||||
};
|
||||
for extension in loader.extensions() {
|
||||
loaders
|
||||
.extension_to_index
|
||||
.extension_to_type_id
|
||||
.insert(extension.to_string(), loader_index);
|
||||
}
|
||||
|
||||
if is_new {
|
||||
loaders.type_name_to_index.insert(type_name, loader_index);
|
||||
loaders.values.push(MaybeAssetLoader::Ready(loader));
|
||||
loaders.type_name_to_type_id.insert(type_name, loader_index);
|
||||
loaders
|
||||
.type_id_to_loader
|
||||
.insert(loader_index, MaybeAssetLoader::Ready(loader));
|
||||
} else {
|
||||
let maybe_loader = std::mem::replace(
|
||||
&mut loaders.values[loader_index],
|
||||
loaders.type_id_to_loader.get_mut(&loader_index).unwrap(),
|
||||
MaybeAssetLoader::Ready(loader.clone()),
|
||||
);
|
||||
match maybe_loader {
|
||||
|
@ -219,12 +221,12 @@ impl AssetServer {
|
|||
) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForExtensionError> {
|
||||
let loader = {
|
||||
let loaders = self.data.loaders.read();
|
||||
let index = *loaders.extension_to_index.get(extension).ok_or_else(|| {
|
||||
let index = *loaders.extension_to_type_id.get(extension).ok_or_else(|| {
|
||||
MissingAssetLoaderForExtensionError {
|
||||
extensions: vec![extension.to_string()],
|
||||
}
|
||||
})?;
|
||||
loaders.values[index].clone()
|
||||
loaders.type_id_to_loader[&index].clone()
|
||||
};
|
||||
|
||||
match loader {
|
||||
|
@ -240,13 +242,13 @@ impl AssetServer {
|
|||
) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForTypeNameError> {
|
||||
let loader = {
|
||||
let loaders = self.data.loaders.read();
|
||||
let index = *loaders.type_name_to_index.get(type_name).ok_or_else(|| {
|
||||
let index = *loaders.type_name_to_type_id.get(type_name).ok_or_else(|| {
|
||||
MissingAssetLoaderForTypeNameError {
|
||||
type_name: type_name.to_string(),
|
||||
}
|
||||
})?;
|
||||
|
||||
loaders.values[index].clone()
|
||||
loaders.type_id_to_loader[&index].clone()
|
||||
};
|
||||
match loader {
|
||||
MaybeAssetLoader::Ready(loader) => Ok(loader),
|
||||
|
@ -279,6 +281,34 @@ impl AssetServer {
|
|||
Err(MissingAssetLoaderForExtensionError { extensions })
|
||||
}
|
||||
|
||||
/// Retrieves the default [`AssetLoader`] for the given [`Asset`] [`TypeId`], if one can be found.
|
||||
pub async fn get_asset_loader_with_asset_type_id<'a>(
|
||||
&self,
|
||||
type_id: TypeId,
|
||||
) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForTypeIdError> {
|
||||
let loader = {
|
||||
let loaders = self.data.loaders.read();
|
||||
loaders
|
||||
.type_id_to_loader
|
||||
.get(&type_id)
|
||||
.ok_or(MissingAssetLoaderForTypeIdError { type_id })?
|
||||
.clone()
|
||||
};
|
||||
|
||||
match loader {
|
||||
MaybeAssetLoader::Ready(loader) => Ok(loader),
|
||||
MaybeAssetLoader::Pending { mut receiver, .. } => Ok(receiver.recv().await.unwrap()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the default [`AssetLoader`] for the given [`Asset`] type, if one can be found.
|
||||
pub async fn get_asset_loader_with_asset_type<'a, A: Asset>(
|
||||
&self,
|
||||
) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForTypeIdError> {
|
||||
self.get_asset_loader_with_asset_type_id(TypeId::of::<A>())
|
||||
.await
|
||||
}
|
||||
|
||||
/// Begins loading an [`Asset`] of type `A` stored at `path`. This will not block on the asset load. Instead,
|
||||
/// it returns a "strong" [`Handle`]. When the [`Asset`] is loaded (and enters [`LoadState::Loaded`]), it will be added to the
|
||||
/// associated [`Assets`] resource.
|
||||
|
@ -427,10 +457,12 @@ impl AssetServer {
|
|||
force: bool,
|
||||
meta_transform: Option<MetaTransform>,
|
||||
) -> Result<UntypedHandle, AssetLoadError> {
|
||||
let asset_type_id = input_handle.as_ref().map(|handle| handle.type_id());
|
||||
|
||||
let path = path.into_owned();
|
||||
let path_clone = path.clone();
|
||||
let (mut meta, loader, mut reader) = self
|
||||
.get_meta_loader_and_reader(&path_clone)
|
||||
.get_meta_loader_and_reader(&path_clone, asset_type_id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
// if there was an input handle, a "load" operation has already started, so we must produce a "failure" event, if
|
||||
|
@ -477,6 +509,11 @@ impl AssetServer {
|
|||
|
||||
let handle = if let Some((handle, should_load)) = handle_result {
|
||||
if path.label().is_none() && handle.type_id() != loader.asset_type_id() {
|
||||
error!(
|
||||
"Expected {:?}, got {:?}",
|
||||
handle.type_id(),
|
||||
loader.asset_type_id()
|
||||
);
|
||||
return Err(AssetLoadError::RequestedHandleTypeMismatch {
|
||||
path: path.into_owned(),
|
||||
requested: handle.type_id(),
|
||||
|
@ -569,7 +606,24 @@ impl AssetServer {
|
|||
let path = path.into().into_owned();
|
||||
IoTaskPool::get()
|
||||
.spawn(async move {
|
||||
if server.data.infos.read().should_reload(&path) {
|
||||
let mut reloaded = false;
|
||||
|
||||
let requests = server
|
||||
.data
|
||||
.infos
|
||||
.read()
|
||||
.get_path_handles(&path)
|
||||
.map(|handle| server.load_internal(Some(handle), path.clone(), true, None))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for result in requests {
|
||||
match result.await {
|
||||
Ok(_) => reloaded = true,
|
||||
Err(err) => error!("{}", err),
|
||||
}
|
||||
}
|
||||
|
||||
if !reloaded && server.data.infos.read().should_reload(&path) {
|
||||
if let Err(err) = server.load_internal(None, path, true, None).await {
|
||||
error!("{}", err);
|
||||
}
|
||||
|
@ -792,7 +846,7 @@ impl AssetServer {
|
|||
/// Returns an active handle for the given path, if the asset at the given path has already started loading,
|
||||
/// or is still "alive".
|
||||
pub fn get_handle<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Option<Handle<A>> {
|
||||
self.get_handle_untyped(path)
|
||||
self.get_path_and_type_id_handle(&path.into(), TypeId::of::<A>())
|
||||
.map(|h| h.typed_debug_checked())
|
||||
}
|
||||
|
||||
|
@ -812,18 +866,58 @@ impl AssetServer {
|
|||
|
||||
/// Returns an active untyped asset id for the given path, if the asset at the given path has already started loading,
|
||||
/// or is still "alive".
|
||||
/// Returns the first ID in the event of multiple assets being registered against a single path.
|
||||
///
|
||||
/// # See also
|
||||
/// [`get_path_ids`][Self::get_path_ids] for all handles.
|
||||
pub fn get_path_id<'a>(&self, path: impl Into<AssetPath<'a>>) -> Option<UntypedAssetId> {
|
||||
let infos = self.data.infos.read();
|
||||
let path = path.into();
|
||||
infos.get_path_id(&path)
|
||||
let mut ids = infos.get_path_ids(&path);
|
||||
ids.next()
|
||||
}
|
||||
|
||||
/// Returns all active untyped asset IDs for the given path, if the assets at the given path have already started loading,
|
||||
/// or are still "alive".
|
||||
/// Multiple IDs will be returned in the event that a single path is used by multiple [`AssetLoader`]'s.
|
||||
pub fn get_path_ids<'a>(&self, path: impl Into<AssetPath<'a>>) -> Vec<UntypedAssetId> {
|
||||
let infos = self.data.infos.read();
|
||||
let path = path.into();
|
||||
infos.get_path_ids(&path).collect()
|
||||
}
|
||||
|
||||
/// Returns an active untyped handle for the given path, if the asset at the given path has already started loading,
|
||||
/// or is still "alive".
|
||||
/// Returns the first handle in the event of multiple assets being registered against a single path.
|
||||
///
|
||||
/// # See also
|
||||
/// [`get_handles_untyped`][Self::get_handles_untyped] for all handles.
|
||||
pub fn get_handle_untyped<'a>(&self, path: impl Into<AssetPath<'a>>) -> Option<UntypedHandle> {
|
||||
let infos = self.data.infos.read();
|
||||
let path = path.into();
|
||||
infos.get_path_handle(&path)
|
||||
let mut handles = infos.get_path_handles(&path);
|
||||
handles.next()
|
||||
}
|
||||
|
||||
/// Returns all active untyped handles for the given path, if the assets at the given path have already started loading,
|
||||
/// or are still "alive".
|
||||
/// Multiple handles will be returned in the event that a single path is used by multiple [`AssetLoader`]'s.
|
||||
pub fn get_handles_untyped<'a>(&self, path: impl Into<AssetPath<'a>>) -> Vec<UntypedHandle> {
|
||||
let infos = self.data.infos.read();
|
||||
let path = path.into();
|
||||
infos.get_path_handles(&path).collect()
|
||||
}
|
||||
|
||||
/// Returns an active untyped handle for the given path and [`TypeId`], if the asset at the given path has already started loading,
|
||||
/// or is still "alive".
|
||||
pub fn get_path_and_type_id_handle(
|
||||
&self,
|
||||
path: &AssetPath,
|
||||
type_id: TypeId,
|
||||
) -> Option<UntypedHandle> {
|
||||
let infos = self.data.infos.read();
|
||||
let path = path.into();
|
||||
infos.get_path_and_type_id_handle(&path, type_id)
|
||||
}
|
||||
|
||||
/// Returns the path for the given `id`, if it has one.
|
||||
|
@ -844,15 +938,15 @@ impl AssetServer {
|
|||
/// real loader is added.
|
||||
pub fn preregister_loader<L: AssetLoader>(&self, extensions: &[&str]) {
|
||||
let mut loaders = self.data.loaders.write();
|
||||
let loader_index = loaders.values.len();
|
||||
let loader_index = TypeId::of::<L::Asset>();
|
||||
let type_name = std::any::type_name::<L>();
|
||||
loaders
|
||||
.preregistered_loaders
|
||||
.insert(type_name, loader_index);
|
||||
loaders.type_name_to_index.insert(type_name, loader_index);
|
||||
loaders.type_name_to_type_id.insert(type_name, loader_index);
|
||||
for extension in extensions {
|
||||
if loaders
|
||||
.extension_to_index
|
||||
.extension_to_type_id
|
||||
.insert(extension.to_string(), loader_index)
|
||||
.is_some()
|
||||
{
|
||||
|
@ -862,8 +956,8 @@ impl AssetServer {
|
|||
let (mut sender, receiver) = async_broadcast::broadcast(1);
|
||||
sender.set_overflow(true);
|
||||
loaders
|
||||
.values
|
||||
.push(MaybeAssetLoader::Pending { sender, receiver });
|
||||
.type_id_to_loader
|
||||
.insert(loader_index, MaybeAssetLoader::Pending { sender, receiver });
|
||||
}
|
||||
|
||||
/// Retrieve a handle for the given path. This will create a handle (and [`AssetInfo`]) if it does not exist
|
||||
|
@ -885,6 +979,7 @@ impl AssetServer {
|
|||
pub(crate) async fn get_meta_loader_and_reader<'a>(
|
||||
&'a self,
|
||||
asset_path: &'a AssetPath<'_>,
|
||||
asset_type_id: Option<TypeId>,
|
||||
) -> Result<
|
||||
(
|
||||
Box<dyn AssetMetaDyn>,
|
||||
|
@ -944,19 +1039,58 @@ impl AssetServer {
|
|||
Ok((meta, loader, reader))
|
||||
}
|
||||
Err(AssetReaderError::NotFound(_)) => {
|
||||
let loader = self.get_path_asset_loader(asset_path).await?;
|
||||
let loader = self.resolve_loader(asset_path, asset_type_id).await?;
|
||||
|
||||
let meta = loader.default_meta();
|
||||
Ok((meta, loader, reader))
|
||||
}
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
} else {
|
||||
let loader = self.get_path_asset_loader(asset_path).await?;
|
||||
let loader = self.resolve_loader(asset_path, asset_type_id).await?;
|
||||
|
||||
let meta = loader.default_meta();
|
||||
Ok((meta, loader, reader))
|
||||
}
|
||||
}
|
||||
|
||||
/// Selects an [`AssetLoader`] for the provided path and (optional) [`Asset`] [`TypeId`].
|
||||
/// Prefers [`TypeId`], and falls back to reading the file extension in the provided [`AssetPath`] otherwise.
|
||||
async fn resolve_loader<'a>(
|
||||
&'a self,
|
||||
asset_path: &'a AssetPath<'_>,
|
||||
asset_type_id: Option<TypeId>,
|
||||
) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForExtensionError> {
|
||||
let loader = 'type_resolution: {
|
||||
let Some(type_id) = asset_type_id else {
|
||||
// If not provided an asset_type_id, type inference is broken
|
||||
break 'type_resolution None;
|
||||
};
|
||||
|
||||
let None = asset_path.label() else {
|
||||
// Labelled sub-assets could be any type, not just the one registered for the loader
|
||||
break 'type_resolution None;
|
||||
};
|
||||
|
||||
let Ok(loader) = self.get_asset_loader_with_asset_type_id(type_id).await else {
|
||||
bevy_log::warn!(
|
||||
"Could not load asset via type_id: no asset loader registered for {:?}",
|
||||
type_id
|
||||
);
|
||||
break 'type_resolution None;
|
||||
};
|
||||
|
||||
Some(loader)
|
||||
};
|
||||
|
||||
let loader = match loader {
|
||||
Some(loader) => loader,
|
||||
None => self.get_path_asset_loader(asset_path).await?,
|
||||
};
|
||||
|
||||
Ok(loader)
|
||||
}
|
||||
|
||||
pub(crate) async fn load_with_meta_loader_and_reader(
|
||||
&self,
|
||||
asset_path: &AssetPath<'_>,
|
||||
|
@ -1045,9 +1179,9 @@ pub fn handle_internal_asset_events(world: &mut World) {
|
|||
current_folder = parent.to_path_buf();
|
||||
let parent_asset_path =
|
||||
AssetPath::from(current_folder.clone()).with_source(source.clone());
|
||||
if let Some(folder_handle) = infos.get_path_handle(&parent_asset_path) {
|
||||
for folder_handle in infos.get_path_handles(&parent_asset_path) {
|
||||
info!("Reloading folder {parent_asset_path} because the content has changed");
|
||||
server.load_folder_internal(folder_handle.id(), parent_asset_path);
|
||||
server.load_folder_internal(folder_handle.id(), parent_asset_path.clone());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1104,10 +1238,10 @@ pub fn handle_internal_asset_events(world: &mut World) {
|
|||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct AssetLoaders {
|
||||
values: Vec<MaybeAssetLoader>,
|
||||
extension_to_index: HashMap<String, usize>,
|
||||
type_name_to_index: HashMap<&'static str, usize>,
|
||||
preregistered_loaders: HashMap<&'static str, usize>,
|
||||
type_id_to_loader: HashMap<TypeId, MaybeAssetLoader>,
|
||||
extension_to_type_id: HashMap<String, TypeId>,
|
||||
type_name_to_type_id: HashMap<&'static str, TypeId>,
|
||||
preregistered_loaders: HashMap<&'static str, TypeId>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -1190,6 +1324,8 @@ pub enum AssetLoadError {
|
|||
#[error(transparent)]
|
||||
MissingAssetLoaderForTypeName(#[from] MissingAssetLoaderForTypeNameError),
|
||||
#[error(transparent)]
|
||||
MissingAssetLoaderForTypeIdError(#[from] MissingAssetLoaderForTypeIdError),
|
||||
#[error(transparent)]
|
||||
AssetReaderError(#[from] AssetReaderError),
|
||||
#[error(transparent)]
|
||||
MissingAssetSourceError(#[from] MissingAssetSourceError),
|
||||
|
@ -1238,6 +1374,13 @@ pub struct MissingAssetLoaderForTypeNameError {
|
|||
type_name: String,
|
||||
}
|
||||
|
||||
/// An error that occurs when an [`AssetLoader`] is not registered for a given [`Asset`] [`TypeId`].
|
||||
#[derive(Error, Debug, Clone)]
|
||||
#[error("no `AssetLoader` found with the ID '{type_id:?}'")]
|
||||
pub struct MissingAssetLoaderForTypeIdError {
|
||||
pub type_id: TypeId,
|
||||
}
|
||||
|
||||
fn format_missing_asset_ext(exts: &[String]) -> String {
|
||||
if !exts.is_empty() {
|
||||
format!(
|
||||
|
|
|
@ -53,12 +53,52 @@ impl AssetLoader for CustomAssetLoader {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Asset, TypePath, Debug)]
|
||||
pub struct Blob {
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BlobAssetLoader;
|
||||
|
||||
/// Possible errors that can be produced by [`CustomAssetLoader`]
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum BlobAssetLoaderError {
|
||||
/// An [IO](std::io) Error
|
||||
#[error("Could not load file: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
impl AssetLoader for BlobAssetLoader {
|
||||
type Asset = Blob;
|
||||
type Settings = ();
|
||||
type Error = BlobAssetLoaderError;
|
||||
|
||||
fn load<'a>(
|
||||
&'a self,
|
||||
reader: &'a mut Reader,
|
||||
_settings: &'a (),
|
||||
_load_context: &'a mut LoadContext,
|
||||
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> {
|
||||
Box::pin(async move {
|
||||
info!("Loading Blob...");
|
||||
let mut bytes = Vec::new();
|
||||
reader.read_to_end(&mut bytes).await?;
|
||||
|
||||
Ok(Blob { bytes })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.init_resource::<State>()
|
||||
.init_asset::<CustomAsset>()
|
||||
.init_asset::<Blob>()
|
||||
.init_asset_loader::<CustomAssetLoader>()
|
||||
.init_asset_loader::<BlobAssetLoader>()
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, print_on_load)
|
||||
.run();
|
||||
|
@ -67,19 +107,55 @@ fn main() {
|
|||
#[derive(Resource, Default)]
|
||||
struct State {
|
||||
handle: Handle<CustomAsset>,
|
||||
other_handle: Handle<CustomAsset>,
|
||||
blob: Handle<Blob>,
|
||||
printed: bool,
|
||||
}
|
||||
|
||||
fn setup(mut state: ResMut<State>, asset_server: Res<AssetServer>) {
|
||||
// Recommended way to load an asset
|
||||
state.handle = asset_server.load("data/asset.custom");
|
||||
|
||||
// File extensions are optional, but are recommended for project management and last-resort inference
|
||||
state.other_handle = asset_server.load("data/asset_no_extension");
|
||||
|
||||
// Will use BlobAssetLoader instead of CustomAssetLoader thanks to type inference
|
||||
state.blob = asset_server.load("data/asset.custom");
|
||||
}
|
||||
|
||||
fn print_on_load(mut state: ResMut<State>, custom_assets: Res<Assets<CustomAsset>>) {
|
||||
fn print_on_load(
|
||||
mut state: ResMut<State>,
|
||||
custom_assets: Res<Assets<CustomAsset>>,
|
||||
blob_assets: Res<Assets<Blob>>,
|
||||
) {
|
||||
let custom_asset = custom_assets.get(&state.handle);
|
||||
if state.printed || custom_asset.is_none() {
|
||||
let other_custom_asset = custom_assets.get(&state.other_handle);
|
||||
let blob = blob_assets.get(&state.blob);
|
||||
|
||||
// Can't print results if the assets aren't ready
|
||||
if state.printed {
|
||||
return;
|
||||
}
|
||||
|
||||
if custom_asset.is_none() {
|
||||
info!("Custom Asset Not Ready");
|
||||
return;
|
||||
}
|
||||
|
||||
if other_custom_asset.is_none() {
|
||||
info!("Other Custom Asset Not Ready");
|
||||
return;
|
||||
}
|
||||
|
||||
if blob.is_none() {
|
||||
info!("Blob Not Ready");
|
||||
return;
|
||||
}
|
||||
|
||||
info!("Custom asset loaded: {:?}", custom_asset.unwrap());
|
||||
info!("Custom asset loaded: {:?}", other_custom_asset.unwrap());
|
||||
info!("Blob Size: {:?} Bytes", blob.unwrap().bytes.len());
|
||||
|
||||
// Once printed, we won't print again
|
||||
state.printed = true;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue