diff --git a/crates/bevy_asset/Cargo.toml b/crates/bevy_asset/Cargo.toml index 5ee5efda57..03389cdd76 100644 --- a/crates/bevy_asset/Cargo.toml +++ b/crates/bevy_asset/Cargo.toml @@ -20,4 +20,5 @@ serde = { version = "1", features = ["derive"] } crossbeam-channel = "0.4.2" anyhow = "1.0" thiserror = "1.0" +log = { version = "0.4", features = ["release_max_level_info"] } notify = { version = "5.0.0-pre.2", optional = true } \ No newline at end of file diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index d8eac6553d..c374a6d653 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -14,6 +14,7 @@ use std::{ }; use thiserror::Error; +pub type AssetVersion = usize; #[derive(Error, Debug)] pub enum AssetServerError { #[error("Asset folder path is not a directory.")] @@ -38,8 +39,32 @@ struct LoaderThread { requests: Arc>>, } +#[derive(Clone, Debug)] +pub struct AssetInfo { + handle_id: HandleId, + path: PathBuf, + load_state: LoadState, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum LoadState { + Loading(AssetVersion), + Loaded(AssetVersion), + Failed(AssetVersion), +} + +impl LoadState { + pub fn get_version(&self) -> AssetVersion { + match *self { + LoadState::Loaded(version) => version, + LoadState::Loading(version) => version, + LoadState::Failed(version) => version, + } + } +} + pub struct AssetServer { - asset_folders: Vec, + asset_folders: RwLock>, loader_threads: RwLock>, max_loader_threads: usize, asset_handlers: Arc>>>, @@ -47,7 +72,8 @@ pub struct AssetServer { loaders: Vec, extension_to_handler_index: HashMap, extension_to_loader_index: HashMap, - path_to_handle: RwLock>, + asset_info: RwLock>, + asset_info_paths: RwLock>, #[cfg(feature = "filesystem_watcher")] filesystem_watcher: Arc>>, } @@ -55,16 +81,17 @@ pub struct AssetServer { impl Default for AssetServer { fn default() -> Self { AssetServer { - max_loader_threads: 4, - asset_folders: Vec::new(), - loader_threads: RwLock::new(Vec::new()), - asset_handlers: Arc::new(RwLock::new(Vec::new())), - loaders: Vec::new(), - extension_to_handler_index: HashMap::new(), - extension_to_loader_index: HashMap::new(), - path_to_handle: RwLock::new(HashMap::default()), #[cfg(feature = "filesystem_watcher")] filesystem_watcher: Arc::new(RwLock::new(None)), + max_loader_threads: 4, + asset_folders: Default::default(), + loader_threads: Default::default(), + asset_handlers: Default::default(), + loaders: Default::default(), + extension_to_handler_index: Default::default(), + extension_to_loader_index: Default::default(), + asset_info_paths: Default::default(), + asset_info: Default::default(), } } } @@ -100,20 +127,20 @@ impl AssetServer { self.loaders.push(resources); } - pub fn load_asset_folder>(&mut self, path: P) -> Result<(), AssetServerError> { + pub fn load_asset_folder>(&self, path: P) -> Result, AssetServerError> { let root_path = self.get_root_path()?; let asset_folder = root_path.join(path); - self.load_assets_in_folder_recursive(&asset_folder)?; - self.asset_folders.push(asset_folder); - Ok(()) + let handle_ids = self.load_assets_in_folder_recursive(&asset_folder)?; + self.asset_folders.write().unwrap().push(asset_folder); + Ok(handle_ids) } pub fn get_handle>(&self, path: P) -> Option> { - self.path_to_handle + self.asset_info_paths .read() .unwrap() .get(path.as_ref()) - .map(|h| Handle::from(*h)) + .map(|handle_id| Handle::from(*handle_id)) } #[cfg(feature = "filesystem_watcher")] @@ -138,8 +165,8 @@ impl AssetServer { let _ = filesystem_watcher.get_or_insert_with(|| FilesystemWatcher::default()); // watch current files - let path_to_handle = self.path_to_handle.read().unwrap(); - for asset_path in path_to_handle.keys() { + let asset_info_paths = self.asset_info_paths.read().unwrap(); + for asset_path in asset_info_paths.keys() { Self::watch_path_for_changes(&mut filesystem_watcher, asset_path)?; } @@ -233,7 +260,6 @@ impl AssetServer { let asset = loader.load_from_file(path)?; let handle = Handle::from(handle_id); assets.set(handle, asset); - assets.set_path(handle, path); Ok(handle) } else { Err(AssetServerError::MissingAssetHandler) @@ -251,13 +277,27 @@ impl AssetServer { .to_str() .expect("Extension should be a valid string."), ) { + let mut new_version = 0; let handle_id = { - let mut path_to_handle = self.path_to_handle.write().unwrap(); - if let Some(handle_id) = path_to_handle.get(path) { - *handle_id + let mut asset_info = self.asset_info.write().unwrap(); + let mut asset_info_paths = self.asset_info_paths.write().unwrap(); + if let Some(asset_info) = asset_info_paths + .get(path) + .and_then(|handle_id| asset_info.get_mut(&handle_id)) + { + asset_info.load_state = if let LoadState::Loaded(_version) = asset_info.load_state { new_version += 1; LoadState::Loading(new_version) } else { LoadState::Loading(new_version) }; + asset_info.handle_id } else { let handle_id = HandleId::new(); - path_to_handle.insert(path.to_owned(), handle_id.clone()); + asset_info.insert( + handle_id, + AssetInfo { + handle_id, + path: path.to_owned(), + load_state: LoadState::Loading(new_version), + }, + ); + asset_info_paths.insert(path.to_owned(), handle_id); handle_id } }; @@ -266,6 +306,7 @@ impl AssetServer { handle_id, path: path.to_owned(), handler_index: *index, + version: new_version, }); // TODO: watching each asset explicitly is a simpler implementation, its possible it would be more efficient to watch @@ -281,7 +322,42 @@ impl AssetServer { } } + pub fn set_load_state(&self, handle_id: HandleId, load_state: LoadState) { + self.asset_info + .write() + .unwrap() + .get_mut(&handle_id) + .map(|asset_info| { + if load_state.get_version() >= asset_info.load_state.get_version() { + asset_info.load_state = load_state; + } + }); + } + + pub fn get_load_state_untyped(&self, handle_id: HandleId) -> Option { + self.asset_info.read().unwrap().get(&handle_id).map(|asset_info| asset_info.load_state.clone()) + } + + pub fn get_load_state(&self, handle: Handle) -> Option { + self.get_load_state_untyped(handle.id) + } + + pub fn get_group_load_state(&self, handle_ids: &[HandleId]) -> Option { + let mut load_state = LoadState::Loaded(0); + for handle_id in handle_ids.iter() { + match self.get_load_state_untyped(*handle_id) { + Some(LoadState::Loaded(_)) => continue, + Some(LoadState::Loading(_)) => {load_state = LoadState::Loading(0);}, + Some(LoadState::Failed(_)) => return Some(LoadState::Failed(0)), + None => return None, + } + } + + Some(load_state) + } + fn send_request_to_loader_thread(&self, load_request: LoadRequest) { + // NOTE: This lock makes the call to Arc::strong_count safe. Removing (or reordering) it could result in undefined behavior let mut loader_threads = self.loader_threads.write().unwrap(); if loader_threads.len() < self.max_loader_threads { let loader_thread = LoaderThread { @@ -330,7 +406,7 @@ impl AssetServer { }); } - fn load_assets_in_folder_recursive(&self, path: &Path) -> Result<(), AssetServerError> { + fn load_assets_in_folder_recursive(&self, path: &Path) -> Result, AssetServerError> { if !path.is_dir() { return Err(AssetServerError::AssetFolderNotADirectory( path.to_str().unwrap().to_string(), @@ -338,21 +414,24 @@ impl AssetServer { } let root_path = self.get_root_path()?; + let mut handle_ids = Vec::new(); for entry in fs::read_dir(path)? { let entry = entry?; let child_path = entry.path(); if !child_path.is_dir() { let relative_child_path = child_path.strip_prefix(&root_path).unwrap(); - let _ = self.load_untyped( + let handle = self.load_untyped( relative_child_path .to_str() .expect("Path should be a valid string"), - ); + )?; + + handle_ids.push(handle); } else { - self.load_assets_in_folder_recursive(&child_path)?; + handle_ids.extend(self.load_assets_in_folder_recursive(&child_path)?); } } - Ok(()) + Ok(handle_ids) } } diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 1a84954c4c..2eeaedd6b6 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -6,10 +6,7 @@ use bevy_app::{AppBuilder, Events, FromResources}; use bevy_core::bytes::Bytes; use bevy_type_registry::RegisterType; use legion::prelude::*; -use std::{ - collections::HashMap, - path::{Path, PathBuf}, -}; +use std::collections::HashMap; pub enum AssetEvent { Created { handle: Handle }, @@ -19,7 +16,6 @@ pub enum AssetEvent { pub struct Assets { assets: HashMap, T>, - paths: HashMap>, events: Events>, } @@ -27,17 +23,12 @@ impl Default for Assets { fn default() -> Self { Assets { assets: HashMap::default(), - paths: HashMap::default(), events: Events::default(), } } } impl Assets { - pub fn get_with_path>(&mut self, path: P) -> Option> { - self.paths.get(path.as_ref()).map(|handle| *handle) - } - pub fn add(&mut self, asset: T) -> Handle { let handle = Handle::new(); self.assets.insert(handle, asset); @@ -68,11 +59,7 @@ impl Assets { handle } - pub fn set_path>(&mut self, handle: Handle, path: P) { - self.paths.insert(path.as_ref().to_owned(), handle); - } - - pub fn get_id(&self, id: HandleId) -> Option<&T> { + pub fn get_with_id(&self, id: HandleId) -> Option<&T> { self.assets.get(&Handle::from_id(id)) } diff --git a/crates/bevy_asset/src/load_request.rs b/crates/bevy_asset/src/load_request.rs index 183bfa4f57..faffb2fcc8 100644 --- a/crates/bevy_asset/src/load_request.rs +++ b/crates/bevy_asset/src/load_request.rs @@ -1,4 +1,4 @@ -use crate::{AssetLoadError, AssetLoader, AssetResult, Handle, HandleId}; +use crate::{AssetLoadError, AssetLoader, AssetResult, AssetVersion, Handle, HandleId}; use anyhow::Result; use crossbeam_channel::Sender; use fs::File; @@ -10,6 +10,7 @@ pub struct LoadRequest { pub path: PathBuf, pub handle_id: HandleId, pub handler_index: usize, + pub version: AssetVersion, } pub trait AssetLoadRequestHandler: Send + Sync + 'static { @@ -54,6 +55,7 @@ where handle: Handle::from(load_request.handle_id), result, path: load_request.path.clone(), + version: load_request.version, }; self.sender .send(asset_result) diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index f0601fdeab..c88b88e4fa 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -1,4 +1,4 @@ -use crate::{Assets, Handle}; +use crate::{AssetServer, Assets, Handle, AssetVersion, LoadState}; use anyhow::Result; use crossbeam_channel::{Receiver, Sender, TryRecvError}; use fs::File; @@ -34,6 +34,7 @@ pub struct AssetResult { pub result: Result, pub handle: Handle, pub path: PathBuf, + pub version: AssetVersion, } pub struct AssetChannel { @@ -50,14 +51,22 @@ impl AssetChannel { pub fn update_asset_storage_system( asset_channel: Res>, + asset_server: Res, mut assets: ResMut>, ) { loop { match asset_channel.receiver.try_recv() { Ok(result) => { - let asset = result.result.unwrap(); - assets.set(result.handle, asset); - assets.set_path(result.handle, &result.path); + match result.result { + Ok(asset) => { + assets.set(result.handle, asset); + asset_server.set_load_state(result.handle.id, LoadState::Loaded(result.version)); + }, + Err(err) => { + asset_server.set_load_state(result.handle.id, LoadState::Failed(result.version)); + log::error!("Failed to load asset: {:?}", err); + } + } } Err(TryRecvError::Empty) => { break; diff --git a/crates/bevy_scene/src/loaded_scenes.rs b/crates/bevy_scene/src/loaded_scenes.rs index b1e2f3ffb0..ee5078803c 100644 --- a/crates/bevy_scene/src/loaded_scenes.rs +++ b/crates/bevy_scene/src/loaded_scenes.rs @@ -27,11 +27,11 @@ impl FromResources for SceneLoader { impl AssetLoader for SceneLoader { fn from_bytes(&self, _asset_path: &Path, bytes: Vec) -> Result { let registry = self.property_type_registry.read().unwrap(); - let mut deserializer = ron::de::Deserializer::from_bytes(&bytes).unwrap(); + let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?; let scene_deserializer = SceneDeserializer { property_type_registry: ®istry, }; - let scene = scene_deserializer.deserialize(&mut deserializer).unwrap(); + let scene = scene_deserializer.deserialize(&mut deserializer)?; Ok(scene) } fn extensions(&self) -> &[&str] {