use std::any::{type_name, Any, TypeId}; use std::borrow::Cow; use std::cell::RefCell; use std::collections::HashMap; use std::convert::Infallible; use std::fmt::{Debug, Display}; use std::hash::{DefaultHasher, Hash, Hasher}; use std::hint::cold_path; use std::marker::PhantomData; use std::path::{Path, PathBuf}; use std::sync::mpsc::{ channel as mpsc_channel, Receiver as MpscReceiver, Sender as MpscSender, }; use std::sync::Arc; use ecs::actions::Actions; use crate::ecs::pair::ChildOf; use crate::ecs::phase::{Phase, PRE_UPDATE as PRE_UPDATE_PHASE}; use crate::ecs::sole::Single; use crate::ecs::{declare_entity, pair, Sole}; use crate::work_queue::{Work, WorkQueue}; declare_entity! { pub HANDLE_ASSETS_PHASE: (Phase, pair!(ChildOf, { *PRE_UPDATE_PHASE })); } pub trait Asset: Send + Sync + 'static { fn dependencies(&self) -> impl Iterator>; } #[derive(Debug)] pub struct Dependency<'a> { label: Label<'a>, asset_ty_id: AssetTypeId, } impl<'a> Dependency<'a> { #[must_use] pub fn new(label: Label<'a>) -> Self { Self { label, asset_ty_id: AssetTypeId::of::(), } } } /// Asset label. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Label<'a> { pub path: Cow<'a, Path>, pub name: Option>, } impl Label<'_> { #[must_use] fn to_owned(&self) -> LabelOwned { LabelOwned { path: self.path.clone().into_owned(), name: self.name.clone().map(Cow::into_owned), } } } impl<'a> From<&'a Path> for Label<'a> { fn from(path: &'a Path) -> Self { Self { path: path.into(), name: None } } } impl From for Label<'_> { fn from(path: PathBuf) -> Self { Self { path: path.into(), name: None } } } impl Display for Label<'_> { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(formatter, "{}", self.path.display())?; if let Some(name) = &self.name { formatter.write_str("::")?; formatter.write_str(name)?; } Ok(()) } } #[derive(Debug, Sole)] pub struct Assets { store: Vec, asset_lookup: RefCell>, importers: hashbrown::HashMap, import_work_queue: WorkQueue, import_work_msg_receiver: MpscReceiver, import_work_msg_sender: MpscSender, events: Events, } impl Assets { #[must_use] pub fn with_capacity(capacity: usize) -> Self { let (import_work_msg_sender, import_work_msg_receiver) = mpsc_channel::(); Self { store: Vec::with_capacity(capacity), asset_lookup: RefCell::new(HashMap::with_capacity(capacity)), importers: hashbrown::HashMap::new(), import_work_queue: WorkQueue::new("asset_importing_work_queue"), import_work_msg_receiver, import_work_msg_sender, events: Events::default(), } } pub fn set_importer( &mut self, func: impl Fn(&mut Submitter<'_>, &Path, Option<&AssetSettings>) -> Result<(), Err>, ) where AssetT: Asset, AssetSettings: 'static, Err: std::error::Error + Send + Sync + 'static, { self.importers .insert(AssetTypeId::of::(), WrappedImporterFn::new(func)); } #[tracing::instrument( skip_all, fields(asset_label, asset_type = %type_name::()) )] pub fn get(&self, handle: &Handle) -> Option<&AssetT> where AssetT: Asset, { let id = handle.id.into_valid()?; let asset_lookup = self.asset_lookup.borrow(); let AssetState::Ready(asset_index, asset_label) = &asset_lookup.get(&id.label_hash)?.state else { return None; }; tracing::Span::current() .record("asset_label", tracing::field::display(&asset_label)); let Some(stored_asset) = self.store.get(*asset_index) else { unreachable!(); }; let Some(asset) = stored_asset.strong.downcast_ref::() else { tracing::error!("Wrong asset type"); return None; }; Some(asset) } #[tracing::instrument(skip_all, fields(label, asset_type = %type_name::()))] pub fn get_handle_to_loaded<'label, AssetT>( &self, label: impl Into>, ) -> Option> where AssetT: Asset, { let label = label.into(); tracing::Span::current().record("label", tracing::field::display(&label)); let label_hash = LabelHash::new(&label); let asset_lookup = self.asset_lookup.borrow(); let AssetState::Ready(asset_index, _) = &asset_lookup.get(&label_hash)?.state else { return None; }; let Some(stored_asset) = self.store.get(*asset_index) else { unreachable!(); }; if stored_asset.strong.downcast_ref::().is_none() { tracing::error!("Wrong asset type"); return None; } Some(Handle::new(label_hash)) } pub fn is_loaded_and_has_type(&self, handle: &Handle) -> bool where AssetT: Asset, { let Some(id) = handle.id.into_valid() else { return false; }; let asset_lookup = self.asset_lookup.borrow(); let Some(AssetState::Ready(asset_index, _)) = asset_lookup.get(&id.label_hash).map(|entry| &entry.state) else { return false; }; let Some(stored_asset) = self.store.get(*asset_index) else { unreachable!(); }; stored_asset.strong.downcast_ref::().is_some() } pub fn get_label(&self, handle: &Handle) -> Option> where AssetT: Asset, { self.get_label_by_id(handle.id) } pub fn get_label_by_id(&self, id: Id) -> Option> { let id = id.into_valid()?; let asset_lookup = self.asset_lookup.borrow(); let AssetState::Ready(_, label) = &asset_lookup.get(&id.label_hash)?.state else { return None; }; Some(label.clone().into_label()) } #[tracing::instrument(skip_all, fields(label, asset_type = %type_name::()))] pub fn load<'i, AssetT>(&self, label: impl Into>) -> Handle where AssetT: Asset, { let label = label.into(); tracing::Span::current().record("label", tracing::field::display(&label)); let label_hash = LabelHash::new(&label); self.load_asset::( AssetTypeId::of::(), label, label_hash, None, ); Handle::new(label_hash) } #[tracing::instrument(skip_all, fields(label, asset_type = %type_name::()))] pub fn load_with_settings<'i, AssetT, AssetSettings>( &self, label: impl Into> + Debug, asset_settings: AssetSettings, ) -> Handle where AssetT: Asset, AssetSettings: Send + Sync + Debug + 'static, { let label = label.into(); tracing::Span::current().record("label", tracing::field::display(&label)); let label_hash = LabelHash::new(&label); self.load_asset( AssetTypeId::of::(), label, label_hash, Some(asset_settings), ); Handle::new(label_hash) } pub fn store_with_name<'name, AssetT>( &mut self, name: impl Into>, asset: AssetT, ) -> Handle where AssetT: Asset, { self.store_with_label( Label { path: Path::new("").into(), name: Some(name.into()), }, asset, ) } pub fn store_with_name_with<'name, AssetT>( &mut self, name: impl Into>, func: impl FnOnce(&mut Self) -> AssetT, ) -> Handle where AssetT: Asset, { let asset = func(self); self.store_with_label( Label { path: Path::new("").into(), name: Some(name.into()), }, asset, ) } #[tracing::instrument(skip_all, fields(label, asset_type = %type_name::()))] pub fn store_with_label<'i, AssetT>( &mut self, label: impl Into>, asset: AssetT, ) -> Handle where AssetT: Asset, { let label = label.into(); tracing::Span::current().record("label", tracing::field::display(&label)); let label_hash = LabelHash::new(&label); let id = Id { inner: IdInner::Valid(IdValid { label_hash }), }; if matches!( self.asset_lookup .get_mut() .get(&label_hash) .map(|entry| &entry.state), Some(AssetState::Ready(..) | AssetState::DependenciesPending { .. }) ) { tracing::error!("Asset already exists"); return Handle::new(label_hash); } let mut missing_dependencies = 0; for dependency in asset.dependencies() { match self .asset_lookup .get_mut() .get_mut(&LabelHash::new(&dependency.label)) { Some(LookupEntry { state: AssetState::Ready(..), .. }) => {} Some(LookupEntry { state: AssetState::Pending | AssetState::DependenciesPending { .. }, waiting_dependants, }) => { missing_dependencies += 1; waiting_dependants.push(id); } None => { missing_dependencies += 1; let dependency_label_hash = LabelHash::new(&dependency.label); self.load_asset::( dependency.asset_ty_id, dependency.label.clone(), dependency_label_hash, None, ); self.asset_lookup .get_mut() .entry(LabelHash::new(&dependency.label)) .or_insert_with(|| LookupEntry { state: AssetState::Pending, waiting_dependants: Vec::new(), }) .waiting_dependants .push(id); } } } tracing::debug!("Storing asset"); self.store.push(StoredAsset::new(asset)); let index = self.store.len() - 1; let label = label.to_owned(); let entry = self .asset_lookup .get_mut() .entry(label_hash) .or_insert_with(|| LookupEntry { state: AssetState::Pending, waiting_dependants: Vec::new(), }); if missing_dependencies != 0 { entry.state = AssetState::DependenciesPending { index, label: label.clone(), remaining_dependencies: missing_dependencies, }; } else { entry.state = AssetState::Ready(index, label.clone()); self.events .curr_tick_events .push(Event::Stored(id, label.into_label())); let waiting_dependants = std::mem::take(&mut entry.waiting_dependants); self.recurse_update_dependants(waiting_dependants); } Handle::from_id(id) } pub fn events(&self) -> &Events { &self.events } fn recurse_update_dependants(&mut self, initial_dependants: Vec) { let mut all_dependants = initial_dependants; loop { let dependants = std::mem::take(&mut all_dependants); if dependants.is_empty() { break; } for dependant_id in dependants { let dependant_label_hash = dependant_id.into_valid().unwrap().label_hash; let Some(LookupEntry { state, waiting_dependants }) = self.asset_lookup.get_mut().get_mut(&dependant_label_hash) else { continue; }; { let AssetState::DependenciesPending { remaining_dependencies, .. } = state else { continue; }; *remaining_dependencies = remaining_dependencies.saturating_sub(1); if *remaining_dependencies > 0 { continue; } } let AssetState::DependenciesPending { index, label, .. } = std::mem::replace(state, AssetState::Pending) else { unreachable!(); }; *state = AssetState::Ready(index, label.clone()); self.events .curr_tick_events .push(Event::Stored(dependant_id, label.into_label())); all_dependants.append(waiting_dependants); } } } fn is_pending(asset_lookup: &HashMap, label: &Label) -> bool { if label.name.is_some() { if let Some(AssetState::Pending | AssetState::DependenciesPending { .. }) = asset_lookup .get(&LabelHash::new(&Label { path: label.path.as_ref().into(), name: None, })) .map(|entry| &entry.state) { return true; } } if let Some(AssetState::Pending | AssetState::DependenciesPending { .. }) = asset_lookup .get(&LabelHash::new(label)) .map(|entry| &entry.state) { return true; } false } fn load_asset( &self, asset_ty_id: AssetTypeId, label: Label<'_>, label_hash: LabelHash, asset_settings: Option, ) where AssetSettings: Send + Sync + Debug + 'static, { let mut asset_lookup = self.asset_lookup.borrow_mut(); if Self::is_pending(&asset_lookup, &label) { return; } self.add_import_work::( asset_ty_id, &label, label_hash, asset_settings, &mut asset_lookup, ); } fn add_import_work( &self, asset_ty_id: AssetTypeId, label: &Label<'_>, label_hash: LabelHash, asset_settings: Option, asset_lookup: &mut HashMap, ) where AssetSettings: Any + Send + Sync, { let Some(importer) = self.importers.get(&asset_ty_id) else { tracing::error!("No importer exists for asset"); return; }; self.import_work_queue.add_work(Work { func: |ImportWorkUserData { import_work_msg_sender, asset_path, asset_settings, importer, }| { if let Err(err) = importer.call( import_work_msg_sender, asset_path.as_path(), asset_settings.as_deref(), ) { tracing::error!( "Failed to import asset {}: {:#}", asset_path.display(), crate::Error::new(err) ); } }, user_data: ImportWorkUserData { import_work_msg_sender: self.import_work_msg_sender.clone(), asset_path: label.path.to_path_buf(), asset_settings: asset_settings.map(|asset_settings| { Box::new(asset_settings) as Box }), importer: importer.clone(), }, }); assert!(!asset_lookup.contains_key(&label_hash)); asset_lookup.insert( label_hash, LookupEntry { state: AssetState::Pending, waiting_dependants: Vec::new(), }, ); if label.name.is_some() { let parent_label_hash = LabelHash::new(&Label { path: label.path.as_ref().into(), name: None, }); assert!(!asset_lookup.contains_key(&parent_label_hash)); asset_lookup.insert( parent_label_hash, LookupEntry { state: AssetState::Pending, waiting_dependants: Vec::new(), }, ); } } } impl Default for Assets { fn default() -> Self { Self::with_capacity(0) } } pub struct Submitter<'path> { import_work_msg_sender: MpscSender, asset_path: &'path Path, } impl Submitter<'_> { pub fn submit_load_other<'label, AssetT>( &self, label: impl Into>, ) -> Handle where AssetT: Asset, { let label = label.into(); let _ = self.import_work_msg_sender.send(ImportWorkMessage::Load { do_load: |assets, label, _asset_settings| { let _ = assets.load::(label); }, label: label.to_owned(), asset_settings: None, }); Handle::new(LabelHash::new(&label)) } pub fn submit_load_other_with_settings<'label, AssetT, AssetSettings>( &self, label: impl Into>, asset_settings: AssetSettings, ) -> Handle where AssetT: Asset, AssetSettings: Send + Sync + Debug + 'static, { let label = label.into(); let _ = self.import_work_msg_sender.send(ImportWorkMessage::Load { do_load: |assets, label, asset_settings| { let asset_settings = *asset_settings .expect("Not possible") .downcast::() .expect("Not possible"); let _ = assets .load_with_settings::(label, asset_settings); }, label: label.to_owned(), asset_settings: Some(Box::new(asset_settings)), }); Handle::new(LabelHash::new(&label)) } pub fn submit_store(&self, asset: AssetT) -> Handle where AssetT: Asset, { let label = LabelOwned { path: self.asset_path.into(), name: None, }; let label_hash = LabelHash::new(&label.as_label()); let _ = self.import_work_msg_sender.send(ImportWorkMessage::Store { do_store: |assets, label, boxed_asset| { let Ok(asset) = boxed_asset.downcast::() else { unreachable!(); }; assets.store_with_label::(label.as_label(), *asset); }, label, asset: Box::new(asset), }); Handle::new(label_hash) } pub fn submit_store_named( &self, name: impl AsRef, asset: AssetT, ) -> Handle where AssetT: Asset, { let label = LabelOwned { path: self.asset_path.into(), name: Some(name.as_ref().into()), }; let label_hash = LabelHash::new(&label.as_label()); let _ = self.import_work_msg_sender.send(ImportWorkMessage::Store { do_store: |assets, label, boxed_asset| { let Ok(asset) = boxed_asset.downcast::() else { unreachable!(); }; assets.store_with_label::(label.as_label(), *asset); }, label, asset: Box::new(asset), }); Handle::new(label_hash) } } /// Asset handle. #[derive(Debug)] pub struct Handle { id: Id, _pd: PhantomData, } impl Handle { pub fn invalid() -> Self { Self::from_id(Id::invalid()) } #[must_use] pub fn from_id(id: Id) -> Self { Self { id, _pd: PhantomData } } #[must_use] pub fn id(&self) -> Id { self.id } fn new(label_hash: LabelHash) -> Self { Self::from_id(Id { inner: IdInner::Valid(IdValid { label_hash }), }) } } impl Clone for Handle { fn clone(&self) -> Self { Self { id: self.id, _pd: PhantomData } } } /// Asset ID. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Id { inner: IdInner, } impl Id { pub fn invalid() -> Self { Self { inner: IdInner::Invalid } } fn into_valid(self) -> Option { match self.inner { IdInner::Valid(id) => Some(id), IdInner::Invalid => None, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] enum IdInner { Valid(IdValid), Invalid, } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] struct IdValid { label_hash: LabelHash, } #[derive(Debug, Default)] pub struct Events { curr_tick_events: Vec, last_tick_events: Vec, } impl Events { pub fn last_tick_events(&self) -> impl Iterator { self.last_tick_events.iter() } } /// Asset event. #[derive(Debug)] pub enum Event { /// Asset stored. Stored(Id, Label<'static>), } #[derive(Debug, thiserror::Error)] enum ImporterError { #[error("Settings has a incorrect type")] IncorrectAssetSettingsType(PathBuf), #[error(transparent)] Other(Box), } #[derive(Debug, Clone)] struct WrappedImporterFn { wrapper_func: fn( MpscSender, &Path, Option<&(dyn Any + Send + Sync)>, ) -> Result<(), ImporterError>, } impl WrappedImporterFn { fn new(inner_func_param: InnerFunc) -> Self where InnerFunc: Fn(&mut Submitter<'_>, &Path, Option<&AssetSettings>) -> Result<(), Err>, AssetSettings: 'static, Err: std::error::Error + Send + Sync + 'static, { assert_eq!(size_of::(), 0); let wrapper_func = |import_work_msg_sender: MpscSender, asset_path: &Path, asset_settings: Option<&(dyn Any + Send + Sync)>| { let inner_func = unsafe { std::mem::zeroed::() }; let asset_settings = asset_settings .map(|asset_settings| { asset_settings .downcast_ref::() .ok_or_else(|| { ImporterError::IncorrectAssetSettingsType( asset_path.to_path_buf(), ) }) }) .transpose()?; inner_func( &mut Submitter { import_work_msg_sender, asset_path }, asset_path, asset_settings, ) .map_err(|err| ImporterError::Other(Box::new(err)))?; Ok(()) }; std::mem::forget(inner_func_param); Self { wrapper_func } } fn call( &self, import_work_msg_sender: MpscSender, asset_path: &Path, asset_settings: Option<&(dyn Any + Send + Sync)>, ) -> Result<(), ImporterError> { (self.wrapper_func)(import_work_msg_sender, asset_path, asset_settings) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] struct LabelHash(u64); impl LabelHash { fn new(label: &Label<'_>) -> Self { let mut hasher = DefaultHasher::new(); label.hash(&mut hasher); Self(hasher.finish()) } } #[derive(Debug, Default)] pub(crate) struct Extension { pub assets: Assets, } impl crate::ecs::extension::Extension for Extension { fn collect(self, mut collector: crate::ecs::extension::Collector<'_>) { let _ = collector.add_sole(self.assets); collector.spawn_declared_entity(&HANDLE_ASSETS_PHASE); collector.add_system(*HANDLE_ASSETS_PHASE, add_received_assets); collector.add_system(*HANDLE_ASSETS_PHASE, check_import_wq_thread_not_panicked); } } fn add_received_assets(mut assets: Single) { let Ok(assets) = assets.get_mut() else { unreachable!(); }; let Events { curr_tick_events, last_tick_events } = &mut assets.events; std::mem::swap(last_tick_events, curr_tick_events); curr_tick_events.clear(); while let Ok(import_work_msg) = assets.import_work_msg_receiver.try_recv() { match import_work_msg { ImportWorkMessage::Store { do_store, label, asset } => { do_store(assets, label, asset); } ImportWorkMessage::Load { do_load, label, asset_settings } => { do_load( assets, Label { path: label.path.as_path().into(), name: label.name.as_deref().map(Into::into), }, asset_settings, ); } } } } fn check_import_wq_thread_not_panicked(assets: Single, mut actions: Actions<'_>) { let Ok(assets) = assets.get() else { unreachable!(); }; if assets.import_work_queue.get_thread_panic().is_some() { cold_path(); actions.stop(); } } #[derive(Debug)] struct ImportWorkUserData { import_work_msg_sender: MpscSender, asset_path: PathBuf, asset_settings: Option>, importer: WrappedImporterFn, } #[derive(Debug)] enum ImportWorkMessage { Store { do_store: fn(&mut Assets, LabelOwned, Box), label: LabelOwned, asset: Box, }, Load { do_load: fn(&Assets, Label<'_>, Option>), label: LabelOwned, asset_settings: Option>, }, } #[derive(Debug)] struct LookupEntry { state: AssetState, waiting_dependants: Vec, } #[derive(Debug)] enum AssetState { Ready(usize, LabelOwned), Pending, DependenciesPending { index: usize, label: LabelOwned, remaining_dependencies: usize, }, } #[derive(Debug)] struct StoredAsset { strong: Arc, } impl StoredAsset { fn new(asset: AssetT) -> Self { let strong = Arc::new(asset); Self { strong } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] struct AssetTypeId { inner: TypeId, } impl AssetTypeId { fn of() -> Self { Self { inner: TypeId::of::() } } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] struct LabelOwned { path: PathBuf, name: Option, } impl LabelOwned { #[must_use] fn as_label(&self) -> Label<'_> { Label { path: self.path.as_path().into(), name: self.name.as_deref().map(Into::into), } } fn into_label(self) -> Label<'static> { Label { path: self.path.into(), name: self.name.map(Into::into), } } } impl Display for LabelOwned { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { as Display>::fmt(&self.as_label(), formatter) } }