use std::any::{type_name, Any, TypeId}; use std::borrow::Cow; use std::cell::RefCell; use std::convert::Infallible; use std::fmt::{Debug, Display}; use std::hash::Hash; use std::marker::PhantomData; use std::num::NonZero; use std::panic::{catch_unwind, RefUnwindSafe, UnwindSafe}; use std::path::{Path, PathBuf}; use std::sync::mpsc::{ channel as mpsc_channel, Receiver as MpscReceiver, Sender as MpscSender, }; use std::thread::available_parallelism; use rayon::{ThreadPool, ThreadPoolBuilder}; 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}; const BACKUP_IMPORT_WORK_THREAD_CNT: usize = 2; declare_entity! { pub HANDLE_ASSETS_PHASE: (Phase, pair!(ChildOf, { *PRE_UPDATE_PHASE })); } pub trait Asset: Send + Sync + 'static { fn visit_dependencies<'this>(&'this self, func: &mut dyn FnMut(Dependency<'this>)); } #[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 status. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[non_exhaustive] pub enum Status { Ready, Pending, Failed(FailureReason), NotFound, } impl Status { #[must_use] pub fn is_ready(self) -> bool { matches!(self, Self::Ready) } #[must_use] pub fn is_pending(self) -> bool { matches!(self, Self::Pending) } #[must_use] pub fn is_failed(self) -> bool { matches!(self, Self::Failed(_)) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[non_exhaustive] pub enum FailureReason { Import, Dependency, } /// Asset label. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Label<'a> { pub path: Cow<'a, Path>, pub name: Option>, } impl Label<'_> { pub fn borrow(&self) -> Label<'_> { Label { path: (&*self.path).into(), name: self.name.as_deref().map(Into::into), } } #[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(Sole)] pub struct Assets { store: store::Store, id_lut: RefCell>, metadata_lut: RefCell>, importers: hashbrown::HashMap, import_work_thread_pool: ThreadPool, import_work_msg_receiver: MpscReceiver, import_work_msg_sender: MpscSender, curr_tick_events: Vec, } impl Assets { #[must_use] #[tracing::instrument(skip_all)] pub(crate) fn with_capacity(capacity: usize) -> Self { let (import_work_msg_sender, import_work_msg_receiver) = mpsc_channel::(); let import_work_thread_cnt = calc_import_work_thread_cnt(); Self { store: store::Store::with_capacity(capacity), id_lut: RefCell::new(hashbrown::HashMap::with_capacity(capacity)), metadata_lut: RefCell::new(hashbrown::HashMap::with_capacity(capacity)), importers: hashbrown::HashMap::new(), import_work_thread_pool: ThreadPoolBuilder::new() .thread_name(|thread_index| format!("asset_import_worker_{thread_index}")) .num_threads(import_work_thread_cnt) .build() .expect("Unable to create thread pool"), import_work_msg_receiver, import_work_msg_sender, curr_tick_events: Vec::with_capacity(capacity), } } pub fn set_importer( &mut self, func: impl Fn(&mut Submitter<'_>, &Path, Option<&AssetSettings>) -> Result<(), Err> + RefUnwindSafe, ) where AssetT: Asset, AssetSettings: UnwindSafe + RefUnwindSafe + 'static, Err: std::error::Error + Send + Sync + 'static, { self.importers .insert(AssetTypeId::of::(), WrappedImporterFn::new(func)); } #[tracing::instrument( skip_all, fields(asset_type = %type_name::()) )] pub fn get(&self, handle: &Handle) -> Option<&AssetT> where AssetT: Asset, { let id = handle.id.into_valid()?; if !self.get_is_asset_ready(id) { return None; } let stored_asset = self.store.get(id.index)?; let Some(asset) = stored_asset.data.downcast_ref::() else { let metadata_lut = self.metadata_lut.borrow(); // A stored asset will always have metadata let asset_metadata = metadata_lut.get(&id).unwrap_or_else(|| unreachable!()); tracing::error!(label = %asset_metadata.label, "Wrong asset type"); return None; }; Some(asset) } #[tracing::instrument( skip_all, fields(asset_type = %type_name::()) )] pub fn get_mut(&mut self, handle: &Handle) -> Option<&mut AssetT> where AssetT: Asset, { let id = handle.id.into_valid()?; if !self.get_is_asset_ready(id) { return None; } let stored_asset = self.store.get_mut(id.index)?; let Some(asset) = stored_asset.data.downcast_mut::() else { let metadata_lut = self.metadata_lut.get_mut(); // A stored asset will always have metadata let asset_metadata = metadata_lut.get(&id).unwrap_or_else(|| unreachable!()); tracing::error!(label = %asset_metadata.label, "Wrong asset type"); return None; }; Some(asset) } #[tracing::instrument(skip_all, fields(asset_type = %type_name::()))] pub fn get_handle_to_loaded<'label, AssetT>( &self, label: impl Into>, ) -> Option> where AssetT: Asset, { let label = label.into(); let Some((Some(stored_asset), id)) = self.get_asset_by_label(&label) else { return None; }; if !self.get_is_asset_ready(id) { return None; } if stored_asset.data.downcast_ref::().is_none() { tracing::error!(%label, "Wrong asset type"); return None; } Some(Handle::from_id(Id::from_valid(id))) } 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 Some(stored_asset) = self.store.get(id.index) else { return false; }; if !self.get_is_asset_ready(id) { return false; } stored_asset.data.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 metadata_lut = self.metadata_lut.borrow(); Some(metadata_lut.get(&id)?.label.clone().into_label()) } pub fn get_status_by_label(&self, label: &Label<'_>) -> Status { let id_lut = self.id_lut.borrow(); let metadata_lut = self.metadata_lut.borrow(); let asset_id = id_lut.get(label); let asset_metadata = asset_id.and_then(|asset_id| metadata_lut.get(asset_id)); let mut asset_exists_and_not_ready = false; if let Some(asset_metadata) = asset_metadata { match &asset_metadata.state { AssetState::DependenciesPending { .. } => { return Status::Pending; } AssetState::Failed { reason, .. } => { return Status::Failed(*reason); } AssetState::Ready => { return Status::Ready; } AssetState::NotStored | AssetState::Loading => { asset_exists_and_not_ready = true; } } } if label.name.is_none() { return if asset_exists_and_not_ready { Status::Pending } else { Status::NotFound }; } let parent_asset_state = (|| { let parent_asset_id = asset_metadata .map(|metadata| match &metadata.relation { AssetRelation::Child { parent } => *parent, AssetRelation::Parent { .. } => { // label.name is Some so the asset can not be a parent unreachable!() } }) .or_else(|| { id_lut .get(&Label { path: label.path.as_ref().into(), name: None, }) .copied() })?; let parent_asset_metadata = metadata_lut.get(&parent_asset_id)?; match &parent_asset_metadata.state { AssetState::Failed { reason, .. } => Some(Status::Failed(*reason)), AssetState::Ready | AssetState::NotStored | AssetState::Loading | AssetState::DependenciesPending { .. } => None, } })(); match parent_asset_state { Some(Status::Failed(FailureReason::Dependency)) => { // It would make no sense for a asset's status to be Status::Failed if the // parent asset has a missing dependency if asset_exists_and_not_ready { Status::Pending } else { Status::NotFound } } Some(parent_asset_state) => parent_asset_state, None if asset_exists_and_not_ready => Status::Pending, None => Status::NotFound, } } #[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 (id, _res) = self.load_asset::(AssetTypeId::of::(), label, None); Handle::from_id(Id::from_valid(id)) } #[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: UnwindSafe + RefUnwindSafe + Send + Sync + Debug + 'static, { let label = label.into(); tracing::Span::current().record("label", tracing::field::display(&label)); let (id, _res) = self.load_asset(AssetTypeId::of::(), label, Some(asset_settings)); Handle::from_id(Id::from_valid(id)) } 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 id = match self.get_asset_by_label(&label) { Some((Some(StoredAsset { .. }) | None, id)) => id, None => IdValid::new(&self.store, &label), }; self.register_asset(id, &label); if self.get_status_by_label(&label) == Status::Ready { tracing::error!("Asset already exists"); return Handle::from_id(Id::from_valid(id)); } let dependencies = { let mut dependencies = Vec::new(); asset.visit_dependencies(&mut |dependency| { dependencies.push(dependency); }); dependencies.sort_by(|dependency_a, dependency_b| { dependency_a.label.cmp(&dependency_b.label) }); dependencies.dedup_by(|dependency_a, dependency_b| { dependency_a.label == dependency_b.label }); dependencies }; let mut missing_dependencies = 0usize; let mut has_failed_dependency = false; for dependency in dependencies { let dependency_status = self.get_status_by_label(&dependency.label); if let Status::Ready = dependency_status { continue; } missing_dependencies += 1; if let Status::Failed(_) = dependency_status { has_failed_dependency = true; } let (dependency_id, res) = self.load_asset::( dependency.asset_ty_id, dependency.label.clone(), None, ); if res.is_err() { has_failed_dependency = true; } let Some(dependency_metadata) = self.metadata_lut.get_mut().get_mut(&dependency_id) else { // Self::load_asset adds metadata if it doesn't already exist unreachable!(); }; if !dependency_metadata.waiting_dependants.contains(&id) { dependency_metadata.waiting_dependants.push(id); } } tracing::debug!("Storing asset"); debug_assert!(self.store.get(id.index).is_none()); self.store .insert(id.index, StoredAsset { data: Box::new(asset) }); let Some(metadata) = self.metadata_lut.get_mut().get_mut(&id) else { unreachable!(); // Metadata is added by Self::register_asset, called above }; if let Some(missing_deps) = NonZero::::new(missing_dependencies) { metadata.state = AssetState::DependenciesPending { remaining_dependencies: missing_deps }; if has_failed_dependency { self.set_failed_asset_states(&label, FailureReason::Dependency); } } else { metadata.state = AssetState::Ready; self.curr_tick_events.push(Event::Stored( Id::from_valid(id), label.to_owned().into_label(), )); let waiting_dependants = std::mem::take(&mut metadata.waiting_dependants); self.recurse_make_dependants_state_ready(waiting_dependants); } Handle::from_id(Id::from_valid(id)) } fn register_asset(&self, id: IdValid, label: &Label<'_>) { let mut id_lut = self.id_lut.borrow_mut(); let mut metadata_lut = self.metadata_lut.borrow_mut(); let parent_id = label.name.is_some().then(|| { let parent_label = Label { path: label.path.as_ref().into(), name: None, }; let parent_id = *id_lut .entry(parent_label.to_owned()) .or_insert_with(|| IdValid::new(&self.store, &parent_label)); let parent_metadata = metadata_lut .entry(parent_id) .or_insert_with(|| AssetMetadata { label: parent_label.to_owned(), waiting_dependants: Vec::new(), relation: AssetRelation::Parent { children: Vec::with_capacity(2), }, state: AssetState::NotStored, }); let AssetRelation::Parent { children: parent_children } = &mut parent_metadata.relation else { // The label of the parent asset has no name so it can not be a child unreachable!(); }; if !parent_children.contains(&id) { parent_children.push(id); } parent_id }); id_lut.insert(label.to_owned(), id); metadata_lut.entry(id).or_insert_with(|| AssetMetadata { label: label.to_owned(), waiting_dependants: Vec::new(), relation: match parent_id { Some(parent_id) => AssetRelation::Child { parent: parent_id }, None => AssetRelation::Parent { children: Vec::new() }, }, state: AssetState::NotStored, }); } fn recurse_make_dependants_state_ready(&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 Some(dependant_metadata) = self.metadata_lut.get_mut().get_mut(&dependant_id) else { continue; }; let prev_remaining_deps = match &dependant_metadata.state { AssetState::NotStored | AssetState::Loading | AssetState::Ready | AssetState::Failed { reason: FailureReason::Import, .. } => { continue; } AssetState::DependenciesPending { remaining_dependencies } => { remaining_dependencies.get() } AssetState::Failed { reason: FailureReason::Dependency, remaining_dependencies, } => *remaining_dependencies, }; let Some(new_remaining_deps) = prev_remaining_deps.checked_sub(1) else { unreachable!(); }; match &mut dependant_metadata.state { AssetState::DependenciesPending { remaining_dependencies } => { debug_assert!(self.store.contains_index(dependant_id.index)); if let Some(new_remaining_deps) = NonZero::::new(new_remaining_deps) { *remaining_dependencies = new_remaining_deps; } else { dependant_metadata.state = AssetState::Ready; } } AssetState::Failed { reason: FailureReason::Dependency, remaining_dependencies, } => { debug_assert!(self.store.contains_index(dependant_id.index)); *remaining_dependencies = new_remaining_deps; if *remaining_dependencies == 0 { dependant_metadata.state = AssetState::Ready; } } _ => unreachable!(), } if new_remaining_deps != 0 { continue; } self.curr_tick_events.push(Event::Stored( Id::from_valid(dependant_id), dependant_metadata.label.clone().into_label(), )); all_dependants.append(&mut dependant_metadata.waiting_dependants); } } } fn set_asset_states(&self, path: &Path, func: impl Fn(AssetState) -> AssetState) { let label = Label { path: path.into(), name: None }; let id = match self.get_asset_by_label(&label) { Some((Some(_) | None, id)) => id, None => IdValid::new(&self.store, &label), }; self.register_asset(id, &label); let mut metadata_lut = self.metadata_lut.borrow_mut(); let Some(metadata) = metadata_lut.get_mut(&id) else { unreachable!(); // Metadata is added by Self::register_asset, called above }; metadata.state = func(std::mem::replace( &mut metadata.state, AssetState::NotStored, )); let AssetRelation::Parent { children } = &metadata.relation else { // label.name is always None so the asset can not be a child unreachable!(); }; for child in children.clone() { let Some(child_asset_metadata) = metadata_lut.get_mut(&child) else { continue; }; child_asset_metadata.state = func(std::mem::replace( &mut child_asset_metadata.state, AssetState::NotStored, )); } } fn set_failed_asset_states(&self, label: &Label<'_>, reason: FailureReason) { let id = match self.get_asset_by_label(label) { Some((Some(_) | None, id)) => id, None => IdValid::new(&self.store, label), }; self.register_asset(id, label); let mut metadata_lut = self.metadata_lut.borrow_mut(); let Some(metadata) = metadata_lut.get_mut(&id) else { unreachable!(); // Metadata is added by Self::register_asset, called above }; let remaining_deps = match &metadata.state { AssetState::Failed { remaining_dependencies, .. } => *remaining_dependencies, AssetState::DependenciesPending { remaining_dependencies } => { remaining_dependencies.get() } AssetState::Ready | AssetState::NotStored | AssetState::Loading => 0, }; metadata.state = AssetState::Failed { reason, remaining_dependencies: remaining_deps, }; let waiting_dependants = metadata.waiting_dependants.clone(); Self::recurse_make_dependants_state_failed(&mut metadata_lut, waiting_dependants); if label.name.is_some() { return; } let Some(metadata) = metadata_lut.get_mut(&id) else { unreachable!(); // Metadata is added by Self::register_asset, called above }; let AssetRelation::Parent { children } = &metadata.relation else { // label.name is None so the asset can not be a child unreachable!(); }; for child in children.clone() { let Some(child_asset_metadata) = metadata_lut.get_mut(&child) else { continue; }; let child_remaining_deps = match &child_asset_metadata.state { AssetState::Failed { .. } | AssetState::Loading => { continue; } AssetState::DependenciesPending { remaining_dependencies } => { remaining_dependencies.get() } AssetState::Ready | AssetState::NotStored => 0, }; child_asset_metadata.state = AssetState::Failed { reason, remaining_dependencies: child_remaining_deps, }; let child_waiting_dependants = child_asset_metadata.waiting_dependants.clone(); Self::recurse_make_dependants_state_failed( &mut metadata_lut, child_waiting_dependants, ); } } fn recurse_make_dependants_state_failed( metadata_lut: &mut hashbrown::HashMap, 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 Some(dependant_metadata) = metadata_lut.get_mut(&dependant_id) else { continue; }; let remaining_deps = match &dependant_metadata.state { AssetState::Failed { .. } | AssetState::Loading => { continue; } AssetState::DependenciesPending { remaining_dependencies } => { remaining_dependencies.get() } AssetState::NotStored | AssetState::Ready => 0, }; dependant_metadata.state = AssetState::Failed { reason: FailureReason::Dependency, remaining_dependencies: remaining_deps, }; all_dependants .extend(dependant_metadata.waiting_dependants.iter().copied()); } } } fn load_asset( &self, asset_ty_id: AssetTypeId, label: Label<'_>, asset_settings: Option, ) -> (IdValid, Result<(), LoadAssetError>) where AssetSettings: UnwindSafe + RefUnwindSafe + Send + Sync + Debug + 'static, { let status = self.get_status_by_label(&label); if let Status::Ready | Status::Pending | Status::Failed(FailureReason::Dependency) = status { let Some(id) = self.id_lut.borrow().get(&label).copied() else { unreachable!(); }; return (id, Ok(())); } let id = match self.id_lut.borrow().get(&label).copied() { Some(id) => id, None => IdValid::new(&self.store, &label), }; let parent_exists = label.name.is_some() && self .id_lut .borrow() .get(&Label { path: label.path.as_ref().into(), name: None, }) .copied() .is_some_and(|parent_id| { let metadata_lut = self.metadata_lut.borrow(); let Some(parent_metadata) = metadata_lut.get(&parent_id) else { unreachable!(); }; match &parent_metadata.state { AssetState::Loading | AssetState::Ready | AssetState::DependenciesPending { .. } | AssetState::Failed { reason: FailureReason::Dependency, .. } => true, AssetState::NotStored | AssetState::Failed { reason: FailureReason::Import, .. } => { false } } }); self.register_asset(id, &label); if parent_exists { return (id, Ok(())); } let Some(importer) = self.importers.get(&asset_ty_id).cloned() else { tracing::error!("No importer exists for asset"); self.set_failed_asset_states(&label, FailureReason::Import); return (id, Err(LoadAssetError::NoImporterFound)); }; let mut metadata_lut = self.metadata_lut.borrow_mut(); let parent_id = metadata_lut.get(&id).and_then(|asset_metadata| { match &asset_metadata.relation { AssetRelation::Child { parent } => Some(*parent), AssetRelation::Parent { .. } => None, } }); if let Some(parent_id) = parent_id { let Some(AssetMetadata { relation: AssetRelation::Parent { children: parent_children }, state: parent_state, .. }) = metadata_lut.get_mut(&parent_id) else { unreachable!(); }; *parent_state = AssetState::Loading; for child_id in parent_children.clone() { let Some(child_metadata) = metadata_lut.get_mut(&child_id) else { continue; }; child_metadata.state = AssetState::Loading; } } tracing::debug!("Adding import work!"); self.add_import_work::( label.path.clone().into_owned(), asset_settings, importer, ); (id, Ok(())) } fn add_import_work( &self, asset_path: PathBuf, asset_settings: Option, importer: WrappedImporterFn, ) where AssetSettings: UnwindSafe + RefUnwindSafe + Any + Send + Sync, { let import_work_msg_sender = self.import_work_msg_sender.clone(); self.import_work_thread_pool.spawn(move || { if let Err(err) = importer.call( &import_work_msg_sender, asset_path.as_path(), asset_settings .as_ref() .map(|asset_settings| asset_settings as &(dyn Any + Send + Sync)), ) { tracing::error!( "Failed to import asset {}: {:#}", asset_path.display(), crate::Error::new(err) ); let _ = import_work_msg_sender.send(ImportWorkMessage::Failed(asset_path)); } }); } fn get_asset_by_label( &self, label: &Label<'_>, ) -> Option<(Option<&StoredAsset>, IdValid)> { let id_lut = self.id_lut.borrow(); let id = *id_lut.get(label)?; Some((self.store.get(id.index), id)) } fn get_is_asset_ready(&self, id: IdValid) -> bool { let metadata_lut = self.metadata_lut.borrow(); let Some(asset_metadata) = metadata_lut.get(&id) else { return false; }; matches!(asset_metadata.state, AssetState::Ready) } } impl Default for Assets { fn default() -> Self { Self::with_capacity(0) } } pub struct Submitter<'a> { storer_funcs: Vec, _pd: PhantomData<&'a ()>, } impl Submitter<'_> { pub fn submit_store(&mut self, asset: AssetT) where AssetT: Asset, { self.storer_funcs.push(Box::new(move |assets, path| { assets.store_with_label::( Label { path: path.into(), name: None }, asset, ); })); } pub fn submit_store_named(&mut self, name: impl AsRef, asset: AssetT) where AssetT: Asset, { let name = name.as_ref().to_owned(); self.storer_funcs.push(Box::new(move |assets, path| { assets.store_with_label::( Label { path: path.into(), name: Some(name.into()), }, asset, ); })); } } /// Asset handle. #[derive(Debug)] pub struct Handle { id: Id, _pd: PhantomData, } impl Handle { #[must_use] 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 } } 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 { #[must_use] pub fn invalid() -> Self { Self { inner: IdInner::Invalid } } fn into_valid(self) -> Option { match self.inner { IdInner::Valid(id) => Some(id), IdInner::Invalid => None, } } fn from_valid(id: IdValid) -> Self { Self { inner: IdInner::Valid(id) } } } #[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 { index: store::ReservedIndex, } impl IdValid { fn new(store: &store::Store, label: &Label<'_>) -> Self { tracing::trace!("Generating valid ID for {label}"); let index = store.reserve_index(); Self { index } } } #[derive(Debug, Default, Sole)] pub struct Events { last_tick: Vec, } impl Events { pub fn iter_last_tick(&self) -> impl Iterator { self.last_tick.iter() } } /// Asset event. #[derive(Debug)] pub enum Event { /// Asset stored. Stored(Id, Label<'static>), // ImportFailed() } #[derive(Debug, thiserror::Error)] enum ImporterError { #[error("Settings has a incorrect type")] IncorrectAssetSettingsType(PathBuf), #[error(transparent)] ErrorResult(Box), #[error("Thread panicked: {0}")] ThreadPanicked(Cow<'static, str>), } #[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> + RefUnwindSafe, AssetSettings: UnwindSafe + RefUnwindSafe + '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()?; let pending_message = catch_unwind(|| { let mut submitter = Submitter { storer_funcs: Vec::with_capacity(2), _pd: PhantomData, }; inner_func(&mut submitter, asset_path, asset_settings) .map_err(|err| ImporterError::ErrorResult(Box::new(err)))?; Result::<_, ImporterError>::Ok(ImportWorkMessage::Completed { storer_funcs: submitter.storer_funcs, path: asset_path.to_path_buf(), }) }) .map_err(|panic_msg| { let panic_msg = if let Some(msg) = panic_msg.downcast_ref::<&'static str>() { Cow::from(*msg) } else if let Ok(msg) = panic_msg.downcast::() { Cow::from(*msg) } else { Cow::from("(unknown panic payload type)") }; ImporterError::ThreadPanicked(panic_msg) })??; let _ = import_work_msg_sender.send(pending_message); 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(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); let _ = collector.add_sole(Events::default()); collector.spawn_declared_entity(&HANDLE_ASSETS_PHASE); collector.add_system(*HANDLE_ASSETS_PHASE, add_received_assets); } } fn add_received_assets(mut assets: Single, mut events: Single) { let Ok(assets) = assets.get_mut() else { unreachable!(); }; let Ok(events) = events.get_mut() else { unreachable!(); }; std::mem::swap(&mut events.last_tick, &mut assets.curr_tick_events); assets.curr_tick_events.clear(); while let Ok(import_work_msg) = assets.import_work_msg_receiver.try_recv() { match import_work_msg { ImportWorkMessage::Completed { storer_funcs, path } => { // It is not guaranteed that all registered assets with this path will be // stored assets.set_asset_states(&path, |curr_state| { if matches!(curr_state, AssetState::Loading) { AssetState::NotStored } else { curr_state } }); for storer in storer_funcs { storer(assets, &path); } } ImportWorkMessage::Failed(asset_path) => { assets.set_failed_asset_states( &Label { path: asset_path.into(), name: None }, FailureReason::Import, ); } } } } type StorerFunc = Box; enum ImportWorkMessage { Completed { storer_funcs: Vec, path: PathBuf, }, Failed(PathBuf), } #[derive(Debug)] struct AssetMetadata { label: LabelOwned, waiting_dependants: Vec, relation: AssetRelation, state: AssetState, } #[derive(Debug)] enum AssetRelation { Parent { children: Vec }, Child { parent: IdValid }, } #[derive(Debug)] enum AssetState { Ready, Loading, DependenciesPending { remaining_dependencies: NonZero, }, Failed { reason: FailureReason, remaining_dependencies: usize, }, NotStored, } struct StoredAsset { data: Box, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] struct AssetTypeId { inner: TypeId, } impl AssetTypeId { fn of() -> Self { Self { inner: TypeId::of::() } } } #[derive(Debug, Clone, Eq)] 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) } } impl PartialEq for LabelOwned { fn eq(&self, other: &Self) -> bool { self.as_label() == other.as_label() } } impl Hash for LabelOwned { fn hash(&self, state: &mut HasherT) { as Hash>::hash(&self.as_label(), state); } } impl hashbrown::Equivalent for Label<'_> { fn equivalent(&self, key: &LabelOwned) -> bool { self == &key.as_label() } } fn calc_import_work_thread_cnt() -> usize { available_parallelism() .map(|avail_parallelism| avail_parallelism.get().div_ceil(2)) .inspect_err(|err| { tracing::warn!( concat!( "Failed to get available parallelism for calculating import work ", "thread count. {} threads will be used. Error: {}" ), BACKUP_IMPORT_WORK_THREAD_CNT, err ); }) .unwrap_or(BACKUP_IMPORT_WORK_THREAD_CNT) } #[derive(Debug)] enum LoadAssetError { NoImporterFound, } mod store { use std::num::NonZero; use std::sync::atomic::{AtomicUsize, Ordering}; use super::StoredAsset; pub struct Store { store: Vec>, next_index: AtomicUsize, } impl Store { pub fn with_capacity(capacity: usize) -> Self { Self { store: Vec::with_capacity(capacity), next_index: AtomicUsize::new(1), } } pub fn contains_index(&self, index: ReservedIndex) -> bool { self.store.get(index.0.get()).is_some_and(Option::is_some) } pub fn get(&self, index: ReservedIndex) -> Option<&StoredAsset> { self.store.get(index.0.get())?.as_ref() } pub fn get_mut(&mut self, index: ReservedIndex) -> Option<&mut StoredAsset> { self.store.get_mut(index.0.get())?.as_mut() } pub fn insert(&mut self, index: ReservedIndex, asset: StoredAsset) { let index = index.0.get(); let curr_len = self.store.len(); if curr_len <= index { let missing = (index + 1) - curr_len; self.store .extend(std::iter::repeat_with(|| None).take(missing)); } self.store[index] = Some(asset); } pub fn reserve_index(&self) -> ReservedIndex { let index = self.next_index.fetch_add(1, Ordering::Relaxed); let index = NonZero::::new(index) .expect("Asset index wrapped around to zero. Too many assets"); ReservedIndex(index) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ReservedIndex(NonZero); }