asset: AssetServer versioning and load group status
This commit is contained in:
parent
9d80b5965e
commit
9a51b3e0fd
@ -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 }
|
||||
@ -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<RwLock<Vec<LoadRequest>>>,
|
||||
}
|
||||
|
||||
#[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<PathBuf>,
|
||||
asset_folders: RwLock<Vec<PathBuf>>,
|
||||
loader_threads: RwLock<Vec<LoaderThread>>,
|
||||
max_loader_threads: usize,
|
||||
asset_handlers: Arc<RwLock<Vec<Box<dyn AssetLoadRequestHandler>>>>,
|
||||
@ -47,7 +72,8 @@ pub struct AssetServer {
|
||||
loaders: Vec<Resources>,
|
||||
extension_to_handler_index: HashMap<String, usize>,
|
||||
extension_to_loader_index: HashMap<String, usize>,
|
||||
path_to_handle: RwLock<HashMap<PathBuf, HandleId>>,
|
||||
asset_info: RwLock<HashMap<HandleId, AssetInfo>>,
|
||||
asset_info_paths: RwLock<HashMap<PathBuf, HandleId>>,
|
||||
#[cfg(feature = "filesystem_watcher")]
|
||||
filesystem_watcher: Arc<RwLock<Option<FilesystemWatcher>>>,
|
||||
}
|
||||
@ -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<P: AsRef<Path>>(&mut self, path: P) -> Result<(), AssetServerError> {
|
||||
pub fn load_asset_folder<P: AsRef<Path>>(&self, path: P) -> Result<Vec<HandleId>, 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<T, P: AsRef<Path>>(&self, path: P) -> Option<Handle<T>> {
|
||||
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<LoadState> {
|
||||
self.asset_info.read().unwrap().get(&handle_id).map(|asset_info| asset_info.load_state.clone())
|
||||
}
|
||||
|
||||
pub fn get_load_state<T>(&self, handle: Handle<T>) -> Option<LoadState> {
|
||||
self.get_load_state_untyped(handle.id)
|
||||
}
|
||||
|
||||
pub fn get_group_load_state(&self, handle_ids: &[HandleId]) -> Option<LoadState> {
|
||||
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<Vec<HandleId>, 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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<T: 'static> {
|
||||
Created { handle: Handle<T> },
|
||||
@ -19,7 +16,6 @@ pub enum AssetEvent<T: 'static> {
|
||||
|
||||
pub struct Assets<T: 'static> {
|
||||
assets: HashMap<Handle<T>, T>,
|
||||
paths: HashMap<PathBuf, Handle<T>>,
|
||||
events: Events<AssetEvent<T>>,
|
||||
}
|
||||
|
||||
@ -27,17 +23,12 @@ impl<T> Default for Assets<T> {
|
||||
fn default() -> Self {
|
||||
Assets {
|
||||
assets: HashMap::default(),
|
||||
paths: HashMap::default(),
|
||||
events: Events::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Assets<T> {
|
||||
pub fn get_with_path<P: AsRef<Path>>(&mut self, path: P) -> Option<Handle<T>> {
|
||||
self.paths.get(path.as_ref()).map(|handle| *handle)
|
||||
}
|
||||
|
||||
pub fn add(&mut self, asset: T) -> Handle<T> {
|
||||
let handle = Handle::new();
|
||||
self.assets.insert(handle, asset);
|
||||
@ -68,11 +59,7 @@ impl<T> Assets<T> {
|
||||
handle
|
||||
}
|
||||
|
||||
pub fn set_path<P: AsRef<Path>>(&mut self, handle: Handle<T>, 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))
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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<T: 'static> {
|
||||
pub result: Result<T, AssetLoadError>,
|
||||
pub handle: Handle<T>,
|
||||
pub path: PathBuf,
|
||||
pub version: AssetVersion,
|
||||
}
|
||||
|
||||
pub struct AssetChannel<T: 'static> {
|
||||
@ -50,14 +51,22 @@ impl<T> AssetChannel<T> {
|
||||
|
||||
pub fn update_asset_storage_system<T>(
|
||||
asset_channel: Res<AssetChannel<T>>,
|
||||
asset_server: Res<AssetServer>,
|
||||
mut assets: ResMut<Assets<T>>,
|
||||
) {
|
||||
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;
|
||||
|
||||
@ -27,11 +27,11 @@ impl FromResources for SceneLoader {
|
||||
impl AssetLoader<Scene> for SceneLoader {
|
||||
fn from_bytes(&self, _asset_path: &Path, bytes: Vec<u8>) -> Result<Scene> {
|
||||
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] {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user