Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Support for Extension-less Assets #10153

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a484322
Added Support for Extenstion-less Assets
bushrat011899 Oct 17, 2023
4a47930
Explicit Error Handling in Asset Loading
bushrat011899 Oct 17, 2023
cf3a9c6
Update assets/data/asset_no_extension
bushrat011899 Oct 17, 2023
097937e
Update crates/bevy_asset/src/server/loaders.rs
bushrat011899 Oct 17, 2023
dd172dc
Added Example of Multiple Assets Using the Same Path
bushrat011899 Oct 17, 2023
4598ae2
Removed Debug Message Accidentally Left Behind
bushrat011899 Oct 17, 2023
8010640
Merge remote-tracking branch 'upstream/main' into AssetLoadingWithout…
bushrat011899 Nov 16, 2023
e3fde60
Fixes for Upstream Merge
bushrat011899 Nov 16, 2023
83e0a25
Fixed up some naming
bushrat011899 Nov 19, 2023
9a43f3b
Fixed `AssetLoader` Resolution and Added Inferred Direct Loading
bushrat011899 Nov 19, 2023
7a10b54
Removed Redundant Error Case in `load_direct_with_reader`
bushrat011899 Nov 22, 2023
5069e31
Updated `TODO` in `AssetInfos::get_or_create_path_handle_internal`
bushrat011899 Nov 22, 2023
85e3915
Updated `AssetInofs::get_path_handles` to return `impl Iterator`
bushrat011899 Nov 22, 2023
80e9287
Updated `AssetInfos::path_to_id` to be a 2-stage `HashMap`
bushrat011899 Nov 23, 2023
45448e9
Updated `AssetServer::get_handle` to avoid iteration
bushrat011899 Nov 23, 2023
190befd
Merge remote-tracking branch 'upstream/main' into AssetLoadingWithout…
bushrat011899 Jan 30, 2024
13a9ead
Resolved Merge Conflicts
bushrat011899 Jan 30, 2024
19f6789
Delete loaders.rs
bushrat011899 Jan 30, 2024
b75dbe4
Clarified File Extension Recommendation
bushrat011899 Jan 31, 2024
63379e8
Fixed Incompatibility with Hot Reloading
bushrat011899 Jan 31, 2024
476ca45
Merge remote-tracking branch 'upstream/main' into AssetLoadingWithout…
bushrat011899 Jan 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions assets/data/asset_no_extension
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CustomAsset (
value: 13
)
5 changes: 1 addition & 4 deletions crates/bevy_asset/src/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 6 additions & 4 deletions crates/bevy_asset/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`].
Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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
Expand Down
98 changes: 78 additions & 20 deletions crates/bevy_asset/src/server/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>>,
bushrat011899 marked this conversation as resolved.
Show resolved Hide resolved
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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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,
Expand Down Expand Up @@ -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_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_handle(&self, path: &AssetPath) -> Option<UntypedHandle> {
let id = *self.path_to_id.get(path)?;
self.get_id_handle(id)
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> {
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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;
Expand All @@ -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
}

Expand Down
Loading