mirror of
https://github.com/bevyengine/bevy
synced 2024-12-04 18:39:13 +00:00
allow asset loader pre-registration (#9429)
# Objective - When loading gltf files during app creation (for example using a FromWorld impl and adding that as a resource), no loader was found. - As the gltf loader can load compressed formats, it needs to know what the GPU supports so it's not available at app creation time. ## Solution alternative to #9426 - add functionality to preregister the loader. loading assets with matching extensions will block until a real loader is registered. - preregister "gltf" and "glb". - prereigster image formats. the way this is set up, if a set of extensions are all registered with a single preregistration call, then later a loader is added that matches some of the extensions, assets using the remaining extensions will then fail. i think that should work well for image formats that we don't know are supported until later.
This commit is contained in:
parent
77507c3af1
commit
bd6764113b
6 changed files with 138 additions and 26 deletions
|
@ -32,6 +32,7 @@ downcast-rs = "1.2.0"
|
||||||
fastrand = "1.7.0"
|
fastrand = "1.7.0"
|
||||||
notify = { version = "6.0.0", optional = true }
|
notify = { version = "6.0.0", optional = true }
|
||||||
parking_lot = "0.12.1"
|
parking_lot = "0.12.1"
|
||||||
|
async-channel = "1.4.2"
|
||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies]
|
[target.'cfg(target_os = "android")'.dependencies]
|
||||||
bevy_winit = { path = "../bevy_winit", version = "0.11.1" }
|
bevy_winit = { path = "../bevy_winit", version = "0.11.1" }
|
||||||
|
|
|
@ -62,6 +62,15 @@ pub(crate) struct AssetRefCounter {
|
||||||
pub(crate) mark_unused_assets: Arc<Mutex<Vec<HandleId>>>,
|
pub(crate) mark_unused_assets: Arc<Mutex<Vec<HandleId>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum MaybeAssetLoader {
|
||||||
|
Ready(Arc<dyn AssetLoader>),
|
||||||
|
Pending {
|
||||||
|
sender: async_channel::Sender<()>,
|
||||||
|
receiver: async_channel::Receiver<()>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
/// Internal data for the asset server.
|
/// Internal data for the asset server.
|
||||||
///
|
///
|
||||||
/// [`AssetServer`] is the public API for interacting with the asset server.
|
/// [`AssetServer`] is the public API for interacting with the asset server.
|
||||||
|
@ -70,7 +79,7 @@ pub struct AssetServerInternal {
|
||||||
pub(crate) asset_ref_counter: AssetRefCounter,
|
pub(crate) asset_ref_counter: AssetRefCounter,
|
||||||
pub(crate) asset_sources: Arc<RwLock<HashMap<SourcePathId, SourceInfo>>>,
|
pub(crate) asset_sources: Arc<RwLock<HashMap<SourcePathId, SourceInfo>>>,
|
||||||
pub(crate) asset_lifecycles: Arc<RwLock<HashMap<Uuid, Box<dyn AssetLifecycle>>>>,
|
pub(crate) asset_lifecycles: Arc<RwLock<HashMap<Uuid, Box<dyn AssetLifecycle>>>>,
|
||||||
loaders: RwLock<Vec<Arc<dyn AssetLoader>>>,
|
loaders: RwLock<Vec<MaybeAssetLoader>>,
|
||||||
extension_to_loader_index: RwLock<HashMap<String, usize>>,
|
extension_to_loader_index: RwLock<HashMap<String, usize>>,
|
||||||
handle_to_path: Arc<RwLock<HashMap<HandleId, AssetPath<'static>>>>,
|
handle_to_path: Arc<RwLock<HashMap<HandleId, AssetPath<'static>>>>,
|
||||||
}
|
}
|
||||||
|
@ -157,6 +166,28 @@ impl AssetServer {
|
||||||
Assets::new(self.server.asset_ref_counter.channel.sender.clone())
|
Assets::new(self.server.asset_ref_counter.channel.sender.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pre-register a loader that will later be added.
|
||||||
|
///
|
||||||
|
/// Assets loaded with matching extensions will be blocked until the
|
||||||
|
/// real loader is added.
|
||||||
|
pub fn preregister_loader(&self, extensions: &[&str]) {
|
||||||
|
let mut loaders = self.server.loaders.write();
|
||||||
|
let loader_index = loaders.len();
|
||||||
|
for extension in extensions {
|
||||||
|
if self
|
||||||
|
.server
|
||||||
|
.extension_to_loader_index
|
||||||
|
.write()
|
||||||
|
.insert(extension.to_string(), loader_index)
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
warn!("duplicate preregistration for `{extension}`, any assets loaded with the previous loader will never complete.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let (sender, receiver) = async_channel::bounded(1);
|
||||||
|
loaders.push(MaybeAssetLoader::Pending { sender, receiver });
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds the provided asset loader to the server.
|
/// Adds the provided asset loader to the server.
|
||||||
///
|
///
|
||||||
/// If `loader` has one or more supported extensions in conflict with loaders that came before
|
/// If `loader` has one or more supported extensions in conflict with loaders that came before
|
||||||
|
@ -166,14 +197,50 @@ impl AssetServer {
|
||||||
T: AssetLoader,
|
T: AssetLoader,
|
||||||
{
|
{
|
||||||
let mut loaders = self.server.loaders.write();
|
let mut loaders = self.server.loaders.write();
|
||||||
let loader_index = loaders.len();
|
let next_loader_index = loaders.len();
|
||||||
|
let mut maybe_existing_loader_index = None;
|
||||||
|
let mut loader_map = self.server.extension_to_loader_index.write();
|
||||||
|
let mut maybe_sender = None;
|
||||||
|
|
||||||
for extension in loader.extensions() {
|
for extension in loader.extensions() {
|
||||||
self.server
|
if let Some(&extension_index) = loader_map.get(*extension) {
|
||||||
.extension_to_loader_index
|
// replacing an existing entry
|
||||||
.write()
|
match maybe_existing_loader_index {
|
||||||
.insert(extension.to_string(), loader_index);
|
None => {
|
||||||
|
match &loaders[extension_index] {
|
||||||
|
MaybeAssetLoader::Ready(_) => {
|
||||||
|
// replacing an existing loader, nothing special to do
|
||||||
|
}
|
||||||
|
MaybeAssetLoader::Pending { sender, .. } => {
|
||||||
|
// the loader was pre-registered, store the channel to notify pending assets
|
||||||
|
maybe_sender = Some(sender.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(index) => {
|
||||||
|
// ensure the loader extensions are consistent
|
||||||
|
if index != extension_index {
|
||||||
|
warn!("inconsistent extensions between loader preregister_loader and add_loader, \
|
||||||
|
loading `{extension}` assets will never complete.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
maybe_existing_loader_index = Some(extension_index);
|
||||||
|
} else {
|
||||||
|
loader_map.insert(extension.to_string(), next_loader_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(existing_index) = maybe_existing_loader_index {
|
||||||
|
loaders[existing_index] = MaybeAssetLoader::Ready(Arc::new(loader));
|
||||||
|
if let Some(sender) = maybe_sender {
|
||||||
|
// notify after replacing the loader
|
||||||
|
let _ = sender.send_blocking(());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loaders.push(MaybeAssetLoader::Ready(Arc::new(loader)));
|
||||||
}
|
}
|
||||||
loaders.push(Arc::new(loader));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a strong handle for an asset with the provided id.
|
/// Gets a strong handle for an asset with the provided id.
|
||||||
|
@ -188,7 +255,7 @@ impl AssetServer {
|
||||||
HandleUntyped::strong(id.into(), sender)
|
HandleUntyped::strong(id.into(), sender)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_asset_loader(&self, extension: &str) -> Result<Arc<dyn AssetLoader>, AssetServerError> {
|
fn get_asset_loader(&self, extension: &str) -> Result<MaybeAssetLoader, AssetServerError> {
|
||||||
let index = {
|
let index = {
|
||||||
// scope map to drop lock as soon as possible
|
// scope map to drop lock as soon as possible
|
||||||
let map = self.server.extension_to_loader_index.read();
|
let map = self.server.extension_to_loader_index.read();
|
||||||
|
@ -204,7 +271,8 @@ impl AssetServer {
|
||||||
fn get_path_asset_loader<P: AsRef<Path>>(
|
fn get_path_asset_loader<P: AsRef<Path>>(
|
||||||
&self,
|
&self,
|
||||||
path: P,
|
path: P,
|
||||||
) -> Result<Arc<dyn AssetLoader>, AssetServerError> {
|
include_pending: bool,
|
||||||
|
) -> Result<MaybeAssetLoader, AssetServerError> {
|
||||||
let s = path
|
let s = path
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.file_name()
|
.file_name()
|
||||||
|
@ -223,9 +291,11 @@ impl AssetServer {
|
||||||
ext = &ext[idx + 1..];
|
ext = &ext[idx + 1..];
|
||||||
exts.push(ext);
|
exts.push(ext);
|
||||||
if let Ok(loader) = self.get_asset_loader(ext) {
|
if let Ok(loader) = self.get_asset_loader(ext) {
|
||||||
|
if include_pending || matches!(loader, MaybeAssetLoader::Ready(_)) {
|
||||||
return Ok(loader);
|
return Ok(loader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Err(AssetServerError::MissingAssetLoader {
|
Err(AssetServerError::MissingAssetLoader {
|
||||||
extensions: exts.into_iter().map(String::from).collect(),
|
extensions: exts.into_iter().map(String::from).collect(),
|
||||||
})
|
})
|
||||||
|
@ -354,12 +424,21 @@ impl AssetServer {
|
||||||
};
|
};
|
||||||
|
|
||||||
// get the according asset loader
|
// get the according asset loader
|
||||||
let asset_loader = match self.get_path_asset_loader(asset_path.path()) {
|
let mut maybe_asset_loader = self.get_path_asset_loader(asset_path.path(), true);
|
||||||
Ok(loader) => loader,
|
|
||||||
|
// if it's still pending, block until notified and refetch the new asset loader
|
||||||
|
if let Ok(MaybeAssetLoader::Pending { receiver, .. }) = maybe_asset_loader {
|
||||||
|
let _ = receiver.recv().await;
|
||||||
|
maybe_asset_loader = self.get_path_asset_loader(asset_path.path(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let asset_loader = match maybe_asset_loader {
|
||||||
|
Ok(MaybeAssetLoader::Ready(loader)) => loader,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
set_asset_failed();
|
set_asset_failed();
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
|
Ok(MaybeAssetLoader::Pending { .. }) => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// load the asset bytes
|
// load the asset bytes
|
||||||
|
@ -492,7 +571,7 @@ impl AssetServer {
|
||||||
if self.asset_io().is_dir(&child_path) {
|
if self.asset_io().is_dir(&child_path) {
|
||||||
handles.extend(self.load_folder(&child_path)?);
|
handles.extend(self.load_folder(&child_path)?);
|
||||||
} else {
|
} else {
|
||||||
if self.get_path_asset_loader(&child_path).is_err() {
|
if self.get_path_asset_loader(&child_path, true).is_err() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let handle =
|
let handle =
|
||||||
|
@ -711,8 +790,11 @@ mod test {
|
||||||
let asset_server = setup(".");
|
let asset_server = setup(".");
|
||||||
asset_server.add_loader(FakePngLoader);
|
asset_server.add_loader(FakePngLoader);
|
||||||
|
|
||||||
let t = asset_server.get_path_asset_loader("test.png");
|
let Ok(MaybeAssetLoader::Ready(t)) = asset_server.get_path_asset_loader("test.png", true) else {
|
||||||
assert_eq!(t.unwrap().extensions()[0], "png");
|
panic!();
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(t.extensions()[0], "png");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -720,14 +802,16 @@ mod test {
|
||||||
let asset_server = setup(".");
|
let asset_server = setup(".");
|
||||||
asset_server.add_loader(FakePngLoader);
|
asset_server.add_loader(FakePngLoader);
|
||||||
|
|
||||||
let t = asset_server.get_path_asset_loader("test.PNG");
|
let Ok(MaybeAssetLoader::Ready(t)) = asset_server.get_path_asset_loader("test.PNG", true) else {
|
||||||
assert_eq!(t.unwrap().extensions()[0], "png");
|
panic!();
|
||||||
|
};
|
||||||
|
assert_eq!(t.extensions()[0], "png");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn no_loader() {
|
fn no_loader() {
|
||||||
let asset_server = setup(".");
|
let asset_server = setup(".");
|
||||||
let t = asset_server.get_path_asset_loader("test.pong");
|
let t = asset_server.get_path_asset_loader("test.pong", true);
|
||||||
assert!(t.is_err());
|
assert!(t.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -736,7 +820,7 @@ mod test {
|
||||||
let asset_server = setup(".");
|
let asset_server = setup(".");
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
match asset_server.get_path_asset_loader("test.v1.2.3.pong") {
|
match asset_server.get_path_asset_loader("test.v1.2.3.pong", true) {
|
||||||
Err(AssetServerError::MissingAssetLoader { extensions }) =>
|
Err(AssetServerError::MissingAssetLoader { extensions }) =>
|
||||||
extensions == vec!["v1.2.3.pong", "2.3.pong", "3.pong", "pong"],
|
extensions == vec!["v1.2.3.pong", "2.3.pong", "3.pong", "pong"],
|
||||||
_ => false,
|
_ => false,
|
||||||
|
@ -771,8 +855,10 @@ mod test {
|
||||||
let asset_server = setup(".");
|
let asset_server = setup(".");
|
||||||
asset_server.add_loader(FakePngLoader);
|
asset_server.add_loader(FakePngLoader);
|
||||||
|
|
||||||
let t = asset_server.get_path_asset_loader("test-v1.2.3.png");
|
let Ok(MaybeAssetLoader::Ready(t)) = asset_server.get_path_asset_loader("test-v1.2.3.png", true) else {
|
||||||
assert_eq!(t.unwrap().extensions()[0], "png");
|
panic!();
|
||||||
|
};
|
||||||
|
assert_eq!(t.extensions()[0], "png");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -780,8 +866,10 @@ mod test {
|
||||||
let asset_server = setup(".");
|
let asset_server = setup(".");
|
||||||
asset_server.add_loader(FakeMultipleDotLoader);
|
asset_server.add_loader(FakeMultipleDotLoader);
|
||||||
|
|
||||||
let t = asset_server.get_path_asset_loader("test.test.png");
|
let Ok(MaybeAssetLoader::Ready(t)) = asset_server.get_path_asset_loader("test.test.png", true) else {
|
||||||
assert_eq!(t.unwrap().extensions()[0], "test.png");
|
panic!();
|
||||||
|
};
|
||||||
|
assert_eq!(t.extensions()[0], "test.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_dir_and_file(file: impl AsRef<Path>) -> tempfile::TempDir {
|
fn create_dir_and_file(file: impl AsRef<Path>) -> tempfile::TempDir {
|
||||||
|
|
|
@ -317,6 +317,10 @@ pub trait AddAsset {
|
||||||
fn add_asset_loader<T>(&mut self, loader: T) -> &mut Self
|
fn add_asset_loader<T>(&mut self, loader: T) -> &mut Self
|
||||||
where
|
where
|
||||||
T: AssetLoader;
|
T: AssetLoader;
|
||||||
|
|
||||||
|
/// Preregisters a loader for the given extensions, that will block asset loads until a real loader
|
||||||
|
/// is registered.
|
||||||
|
fn preregister_asset_loader(&mut self, extensions: &[&str]) -> &mut Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AddAsset for App {
|
impl AddAsset for App {
|
||||||
|
@ -404,6 +408,13 @@ impl AddAsset for App {
|
||||||
self.world.resource_mut::<AssetServer>().add_loader(loader);
|
self.world.resource_mut::<AssetServer>().add_loader(loader);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn preregister_asset_loader(&mut self, extensions: &[&str]) -> &mut Self {
|
||||||
|
self.world
|
||||||
|
.resource_mut::<AssetServer>()
|
||||||
|
.preregister_loader(extensions);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads an internal asset from a project source file.
|
/// Loads an internal asset from a project source file.
|
||||||
|
|
|
@ -44,7 +44,8 @@ impl Plugin for GltfPlugin {
|
||||||
.add_asset::<Gltf>()
|
.add_asset::<Gltf>()
|
||||||
.add_asset::<GltfNode>()
|
.add_asset::<GltfNode>()
|
||||||
.add_asset::<GltfPrimitive>()
|
.add_asset::<GltfPrimitive>()
|
||||||
.add_asset::<GltfMesh>();
|
.add_asset::<GltfMesh>()
|
||||||
|
.preregister_asset_loader(&["gltf", "glb"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&self, app: &mut App) {
|
fn finish(&self, app: &mut App) {
|
||||||
|
|
|
@ -17,7 +17,7 @@ pub struct ImageTextureLoader {
|
||||||
supported_compressed_formats: CompressedImageFormats,
|
supported_compressed_formats: CompressedImageFormats,
|
||||||
}
|
}
|
||||||
|
|
||||||
const FILE_EXTENSIONS: &[&str] = &[
|
pub(crate) const IMG_FILE_EXTENSIONS: &[&str] = &[
|
||||||
#[cfg(feature = "basis-universal")]
|
#[cfg(feature = "basis-universal")]
|
||||||
"basis",
|
"basis",
|
||||||
#[cfg(feature = "bmp")]
|
#[cfg(feature = "bmp")]
|
||||||
|
@ -73,7 +73,7 @@ impl AssetLoader for ImageTextureLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extensions(&self) -> &[&str] {
|
fn extensions(&self) -> &[&str] {
|
||||||
FILE_EXTENSIONS
|
IMG_FILE_EXTENSIONS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -96,6 +96,17 @@ impl Plugin for ImagePlugin {
|
||||||
update_texture_cache_system.in_set(RenderSet::Cleanup),
|
update_texture_cache_system.in_set(RenderSet::Cleanup),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(
|
||||||
|
feature = "png",
|
||||||
|
feature = "dds",
|
||||||
|
feature = "tga",
|
||||||
|
feature = "jpeg",
|
||||||
|
feature = "bmp",
|
||||||
|
feature = "basis-universal",
|
||||||
|
feature = "ktx2",
|
||||||
|
))]
|
||||||
|
app.preregister_asset_loader(IMG_FILE_EXTENSIONS);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&self, app: &mut App) {
|
fn finish(&self, app: &mut App) {
|
||||||
|
|
Loading…
Reference in a new issue