From 885f025ae2310d5e9b3de641224806fa1e996886 Mon Sep 17 00:00:00 2001 From: HampusM Date: Sat, 10 Oct 2026 16:51:54 +0200 Subject: feat(engine): add asset failure state --- engine/src/asset.rs | 1017 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 728 insertions(+), 289 deletions(-) (limited to 'engine/src/asset.rs') diff --git a/engine/src/asset.rs b/engine/src/asset.rs index e1f699a..570a0c7 100644 --- a/engine/src/asset.rs +++ b/engine/src/asset.rs @@ -5,6 +5,7 @@ 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::{ @@ -51,6 +52,46 @@ impl<'a> Dependency<'a> } } +/// 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> @@ -110,11 +151,12 @@ impl Display for Label<'_> } } -#[derive(Debug, Sole)] +#[derive(Sole)] pub struct Assets { store: store::Store, - metadata_lut: RefCell>, + id_lut: RefCell>, + metadata_lut: RefCell>, importers: hashbrown::HashMap, import_work_thread_pool: ThreadPool, import_work_msg_receiver: MpscReceiver, @@ -135,6 +177,7 @@ impl Assets 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() @@ -150,7 +193,8 @@ impl Assets pub fn set_importer( &mut self, - func: impl Fn(&mut Submitter<'_>, &Path, Option<&AssetSettings>) -> Result<(), Err>, + func: impl Fn(&mut Submitter<'_>, &Path, Option<&AssetSettings>) -> Result<(), Err> + + RefUnwindSafe, ) where AssetT: Asset, AssetSettings: UnwindSafe + RefUnwindSafe + 'static, @@ -162,7 +206,7 @@ impl Assets #[tracing::instrument( skip_all, - fields(asset_label, asset_type = %type_name::()) + fields(asset_type = %type_name::()) )] pub fn get(&self, handle: &Handle) -> Option<&AssetT> where @@ -170,17 +214,20 @@ impl Assets { let id = handle.id.into_valid()?; + if !self.get_is_asset_ready(id) { + return None; + } + let stored_asset = self.store.get(id.index)?; - tracing::Span::current() - .record("asset_label", tracing::field::display(&stored_asset.label)); + let Some(asset) = stored_asset.data.downcast_ref::() else { + let metadata_lut = self.metadata_lut.borrow(); - let AssetState::Ready { data: asset_data } = &stored_asset.state else { - return None; - }; + // 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"); - let Some(asset) = asset_data.downcast_ref::() else { - tracing::error!("Wrong asset type"); return None; }; @@ -189,7 +236,7 @@ impl Assets #[tracing::instrument( skip_all, - fields(asset_label, asset_type = %type_name::()) + fields(asset_type = %type_name::()) )] pub fn get_mut(&mut self, handle: &Handle) -> Option<&mut AssetT> where @@ -197,24 +244,27 @@ impl Assets { let id = handle.id.into_valid()?; + if !self.get_is_asset_ready(id) { + return None; + } + let stored_asset = self.store.get_mut(id.index)?; - tracing::Span::current() - .record("asset_label", tracing::field::display(&stored_asset.label)); + let Some(asset) = stored_asset.data.downcast_mut::() else { + let metadata_lut = self.metadata_lut.get_mut(); - let AssetState::Ready { data: asset_data } = &mut stored_asset.state else { - return None; - }; + // 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"); - let Some(asset) = asset_data.downcast_mut::() else { - tracing::error!("Wrong asset type"); return None; }; Some(asset) } - #[tracing::instrument(skip_all, fields(label, asset_type = %type_name::()))] + #[tracing::instrument(skip_all, fields(asset_type = %type_name::()))] pub fn get_handle_to_loaded<'label, AssetT>( &self, label: impl Into>, @@ -224,20 +274,16 @@ impl Assets { let label = label.into(); - tracing::Span::current().record("label", tracing::field::display(&label)); - - let (stored_asset, id) = self.get_asset_by_label(&label)?; - - let Some(StoredAsset { - state: AssetState::Ready { data: asset_data }, - .. - }) = stored_asset - else { + let Some((Some(stored_asset), id)) = self.get_asset_by_label(&label) else { return None; }; - if asset_data.downcast_ref::().is_none() { - tracing::error!("Wrong asset type"); + 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; } @@ -252,15 +298,15 @@ impl Assets return false; }; - let Some(StoredAsset { - state: AssetState::Ready { data: asset_data }, - .. - }) = self.store.get(id.index) - else { + let Some(stored_asset) = self.store.get(id.index) else { return false; }; - asset_data.downcast_ref::().is_some() + if !self.get_is_asset_ready(id) { + return false; + } + + stored_asset.data.downcast_ref::().is_some() } pub fn get_label(&self, handle: &Handle) -> Option> @@ -274,9 +320,91 @@ impl Assets { let id = id.into_valid()?; - let stored_asset = self.store.get(id.index)?; + 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, + } + })(); - Some(stored_asset.label.clone().into_label()) + 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::()))] @@ -288,7 +416,8 @@ impl Assets tracing::Span::current().record("label", tracing::field::display(&label)); - let id = self.load_asset::(AssetTypeId::of::(), label, None); + let (id, _res) = + self.load_asset::(AssetTypeId::of::(), label, None); Handle::from_id(Id::from_valid(id)) } @@ -307,7 +436,7 @@ impl Assets tracing::Span::current().record("label", tracing::field::display(&label)); - let id = + let (id, _res) = self.load_asset(AssetTypeId::of::(), label, Some(asset_settings)); Handle::from_id(Id::from_valid(id)) @@ -363,128 +492,163 @@ impl Assets tracing::Span::current().record("label", tracing::field::display(&label)); let id = match self.get_asset_by_label(&label) { - Some(( - Some(StoredAsset { - state: - AssetState::Ready { .. } | AssetState::DependenciesPending { .. }, - .. - }), - id, - )) => { - tracing::error!("Asset already exists"); - - return Handle::from_id(Id::from_valid(id)); - } - Some((Some(StoredAsset { state: AssetState::Pending, .. }) | None, id)) => id, + Some((Some(StoredAsset { .. }) | None, id)) => id, None => IdValid::new(&self.store, &label), }; - let mut missing_dependencies = 0; + self.register_asset(id, &label); - let mut dependencies = Vec::new(); + if self.get_status_by_label(&label) == Status::Ready { + tracing::error!("Asset already exists"); - asset.visit_dependencies(&mut |dependency| { - dependencies.push(dependency); - }); + 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 { - match self.get_asset_by_label_mut(&dependency.label) { - Some((Some(StoredAsset { state: AssetState::Ready { .. }, .. }), _)) => {} - Some(( - Some(StoredAsset { - state: - AssetState::Pending | AssetState::DependenciesPending { .. }, - waiting_dependants, - .. - }), - _, - )) => { - tracing::trace!( - dependency = %dependency.label, - "Asset has missing dependency" - ); - - missing_dependencies += 1; - - waiting_dependants.push(id); - } - Some((None, _)) | None => { - tracing::trace!( - dependency = %dependency.label, - "Asset has missing dependency" - ); - - missing_dependencies += 1; - - let dependency_id = self.load_asset::( - dependency.asset_ty_id, - dependency.label.clone(), - None, - ); - - self.store - .get_or_insert_with(dependency_id.index, || StoredAsset { - state: AssetState::Pending, - label: dependency.label.to_owned(), - waiting_dependants: Vec::new(), - }) - .waiting_dependants - .push(id); - } + 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"); - let stored_asset = self.store.get_or_insert_with(id.index, || StoredAsset { - state: AssetState::Pending, - label: label.to_owned(), - waiting_dependants: Vec::new(), - }); + debug_assert!(self.store.get(id.index).is_none()); - let label = label.to_owned(); + self.store + .insert(id.index, StoredAsset { data: Box::new(asset) }); - if missing_dependencies != 0 { - stored_asset.state = AssetState::DependenciesPending { - data: Box::new(asset), - remaining_dependencies: missing_dependencies, - }; + 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 { - stored_asset.state = AssetState::Ready { data: Box::new(asset) }; + metadata.state = AssetState::Ready; self.curr_tick_events.push(Event::Stored( Id::from_valid(id), - label.clone().into_label(), + label.to_owned().into_label(), )); - let waiting_dependants = std::mem::take(&mut stored_asset.waiting_dependants); + let waiting_dependants = std::mem::take(&mut metadata.waiting_dependants); - self.recurse_update_dependants(waiting_dependants); + self.recurse_make_dependants_state_ready(waiting_dependants); } - let metadata_lut = self.metadata_lut.get_mut(); + 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_path().into(), + path: label.path.as_ref().into(), name: None, }; - metadata_lut + let parent_id = *id_lut .entry(parent_label.to_owned()) - .or_insert_with(|| Metadata { - id: IdValid::new(&self.store, &parent_label), - parent_id: None, - }) - .id + .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 }); - metadata_lut.insert(label.clone(), Metadata { id, parent_id }); + id_lut.insert(label.to_owned(), id); - Handle::from_id(Id::from_valid(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_update_dependants(&mut self, initial_dependants: Vec) + fn recurse_make_dependants_state_ready(&mut self, initial_dependants: Vec) { let mut all_dependants = initial_dependants; @@ -496,39 +660,223 @@ impl Assets } for dependant_id in dependants { - let Some(dependant_asset) = self.store.get_mut(dependant_id.index) else { + let Some(dependant_metadata) = + self.metadata_lut.get_mut().get_mut(&dependant_id) + else { continue; }; - { - let AssetState::DependenciesPending { - remaining_dependencies, .. - } = &mut dependant_asset.state - else { - continue; - }; - - *remaining_dependencies = remaining_dependencies.saturating_sub(1); - - if *remaining_dependencies > 0 { + 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 AssetState::DependenciesPending { data: dependant_data, .. } = - std::mem::replace(&mut dependant_asset.state, AssetState::Pending) - else { + let Some(new_remaining_deps) = prev_remaining_deps.checked_sub(1) else { unreachable!(); }; - dependant_asset.state = AssetState::Ready { data: dependant_data }; + 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_asset.label.clone().into_label(), + dependant_metadata.label.clone().into_label(), )); - all_dependants.append(&mut dependant_asset.waiting_dependants); + 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()); } } } @@ -538,97 +886,139 @@ impl Assets asset_ty_id: AssetTypeId, label: Label<'_>, asset_settings: Option, - ) -> IdValid + ) -> (IdValid, Result<(), LoadAssetError>) where AssetSettings: UnwindSafe + RefUnwindSafe + Send + Sync + Debug + 'static, { - let id = match self.get_asset_by_label(&label) { - Some((Some(_), id)) => { - return id; - } - Some((None, id)) => id, + 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 mut metadata_lut = self.metadata_lut.borrow_mut(); + 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 mut parent_exists = false; + let Some(parent_metadata) = metadata_lut.get(&parent_id) else { + unreachable!(); + }; - let parent_id = label.name.is_some().then(|| { - let parent_label = Label { - path: label.path.as_ref().into(), - name: None, - }; + match &parent_metadata.state { + AssetState::Loading + | AssetState::Ready + | AssetState::DependenciesPending { .. } + | AssetState::Failed { + reason: FailureReason::Dependency, .. + } => true, + AssetState::NotStored + | AssetState::Failed { reason: FailureReason::Import, .. } => { + false + } + } + }); - parent_exists = metadata_lut.contains_key(&parent_label); + self.register_asset(id, &label); - metadata_lut - .entry(parent_label.to_owned()) - .or_insert_with(|| Metadata { - id: IdValid::new(&self.store, &parent_label), - parent_id: None, - }) - .id + 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, + } }); - metadata_lut.insert(label.to_owned(), Metadata { id, parent_id }); + 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; - if parent_exists { - return id; + for child_id in parent_children.clone() { + let Some(child_metadata) = metadata_lut.get_mut(&child_id) else { + continue; + }; + + child_metadata.state = AssetState::Loading; + } } - self.add_import_work::(asset_ty_id, &label, asset_settings); + tracing::debug!("Adding import work!"); - id + self.add_import_work::( + label.path.clone().into_owned(), + asset_settings, + importer, + ); + + (id, Ok(())) } fn add_import_work( &self, - asset_ty_id: AssetTypeId, - label: &Label<'_>, + asset_path: PathBuf, asset_settings: Option, + importer: WrappedImporterFn, ) where AssetSettings: UnwindSafe + RefUnwindSafe + Any + Send + Sync, { - let Some(importer) = self.importers.get(&asset_ty_id).cloned() else { - tracing::error!("No importer exists for asset"); - return; - }; - let import_work_msg_sender = self.import_work_msg_sender.clone(); - let asset_path = label.path.to_path_buf(); - self.import_work_thread_pool.spawn(move || { - let result = catch_unwind(|| { - 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) - ); - } - }); - - if let Err(panic_msg) = result { - let panic_msg = if let Some(msg) = panic_msg.downcast_ref::<&str>() { - msg - } else if let Some(msg) = panic_msg.downcast_ref::() { - msg.as_str() - } else { - "(unknown panic payload type)" - }; - + 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 {}: Thread panicked: {panic_msg}", + "Failed to import asset {}: {:#}", asset_path.display(), + crate::Error::new(err) ); + + let _ = + import_work_msg_sender.send(ImportWorkMessage::Failed(asset_path)); } }); } @@ -638,23 +1028,22 @@ impl Assets label: &Label<'_>, ) -> Option<(Option<&StoredAsset>, IdValid)> { - let metadata_lut = self.metadata_lut.borrow(); + let id_lut = self.id_lut.borrow(); - let id = metadata_lut.get(label)?.id; + let id = *id_lut.get(label)?; Some((self.store.get(id.index), id)) } - fn get_asset_by_label_mut( - &mut self, - label: &Label<'_>, - ) -> Option<(Option<&mut StoredAsset>, IdValid)> + fn get_is_asset_ready(&self, id: IdValid) -> bool { - let metadata_lut = self.metadata_lut.get_mut(); + let metadata_lut = self.metadata_lut.borrow(); - let id = metadata_lut.get(label)?.id; + let Some(asset_metadata) = metadata_lut.get(&id) else { + return false; + }; - Some((self.store.get_mut(id.index), id)) + matches!(asset_metadata.state, AssetState::Ready) } } @@ -666,11 +1055,10 @@ impl Default for Assets } } -pub struct Submitter<'path> +pub struct Submitter<'a> { - pending_messages: Vec, - import_work_msg_sender: MpscSender, - asset_path: &'path Path, + storer_funcs: Vec, + _pd: PhantomData<&'a ()>, } impl Submitter<'_> @@ -679,41 +1067,29 @@ impl Submitter<'_> where AssetT: Asset, { - let label = LabelOwned { - path: self.asset_path.into(), - name: None, - }; - - self.pending_messages.push(ImportWorkMessage::Store { - do_store: Box::new(move |assets, label| { - assets.store_with_label::(label.as_label(), asset); - }), - label, - }); + 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 label = LabelOwned { - path: self.asset_path.into(), - name: Some(name.as_ref().into()), - }; - - self.pending_messages.push(ImportWorkMessage::Store { - do_store: Box::new(move |assets, label| { - assets.store_with_label::(label.as_label(), asset); - }), - label, - }); - } - - fn perform_submissions(&mut self) - { - for msg in self.pending_messages.drain(..) { - let _ = self.import_work_msg_sender.send(msg); - } + 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, + ); + })); } } @@ -828,6 +1204,7 @@ pub enum Event { /// Asset stored. Stored(Id, Label<'static>), + // ImportFailed() } #[derive(Debug, thiserror::Error)] @@ -837,14 +1214,17 @@ enum ImporterError IncorrectAssetSettingsType(PathBuf), #[error(transparent)] - Other(Box), + ErrorResult(Box), + + #[error("Thread panicked: {0}")] + ThreadPanicked(Cow<'static, str>), } #[derive(Debug, Clone)] struct WrappedImporterFn { wrapper_func: fn( - MpscSender, + &MpscSender, &Path, Option<&(dyn Any + Send + Sync)>, ) -> Result<(), ImporterError>, @@ -854,15 +1234,15 @@ impl WrappedImporterFn { fn new(inner_func_param: InnerFunc) -> Self where - InnerFunc: - Fn(&mut Submitter<'_>, &Path, Option<&AssetSettings>) -> Result<(), Err>, + 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, + |import_work_msg_sender: &MpscSender, asset_path: &Path, asset_settings: Option<&(dyn Any + Send + Sync)>| { let inner_func = unsafe { std::mem::zeroed::() }; @@ -879,16 +1259,34 @@ impl WrappedImporterFn }) .transpose()?; - let mut submitter = Submitter { - pending_messages: Vec::with_capacity(2), - import_work_msg_sender, - asset_path, - }; + 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)") + }; - inner_func(&mut submitter, asset_path, asset_settings) - .map_err(|err| ImporterError::Other(Box::new(err)))?; + ImporterError::ThreadPanicked(panic_msg) + })??; - submitter.perform_submissions(); + let _ = import_work_msg_sender.send(pending_message); Ok(()) }; @@ -900,7 +1298,7 @@ impl WrappedImporterFn fn call( &self, - import_work_msg_sender: MpscSender, + import_work_msg_sender: &MpscSender, asset_path: &Path, asset_settings: Option<&(dyn Any + Send + Sync)>, ) -> Result<(), ImporterError> @@ -909,7 +1307,7 @@ impl WrappedImporterFn } } -#[derive(Debug, Default)] +#[derive(Default)] pub(crate) struct Extension { pub assets: Assets, @@ -944,50 +1342,85 @@ fn add_received_assets(mut assets: Single, mut events: Single) while let Ok(import_work_msg) = assets.import_work_msg_receiver.try_recv() { match import_work_msg { - ImportWorkMessage::Store { do_store, label } => { - do_store(assets, label); + 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 { - Store + Completed { - do_store: Box, - label: LabelOwned, + storer_funcs: Vec, + path: PathBuf, }, + Failed(PathBuf), } #[derive(Debug)] -struct Metadata +struct AssetMetadata { - id: IdValid, - parent_id: Option, + label: LabelOwned, + waiting_dependants: Vec, + relation: AssetRelation, + state: AssetState, } #[derive(Debug)] -enum AssetState +enum AssetRelation { - Ready + Parent { - data: Box, + children: Vec }, - Pending, + Child + { + parent: IdValid + }, +} + +#[derive(Debug)] +enum AssetState +{ + Ready, + Loading, DependenciesPending { - data: Box, + remaining_dependencies: NonZero, + }, + Failed + { + reason: FailureReason, remaining_dependencies: usize, }, + NotStored, } -#[derive(Debug)] struct StoredAsset { - state: AssetState, - label: LabelOwned, - waiting_dependants: Vec, + data: Box, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -1080,6 +1513,12 @@ fn calc_import_work_thread_cnt() -> usize .unwrap_or(BACKUP_IMPORT_WORK_THREAD_CNT) } +#[derive(Debug)] +enum LoadAssetError +{ + NoImporterFound, +} + mod store { use std::num::NonZero; @@ -1087,7 +1526,6 @@ mod store use super::StoredAsset; - #[derive(Debug)] pub struct Store { store: Vec>, @@ -1104,6 +1542,11 @@ mod store } } + 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() @@ -1114,11 +1557,7 @@ mod store self.store.get_mut(index.0.get())?.as_mut() } - pub fn get_or_insert_with( - &mut self, - index: ReservedIndex, - func: impl FnOnce() -> StoredAsset, - ) -> &mut StoredAsset + pub fn insert(&mut self, index: ReservedIndex, asset: StoredAsset) { let index = index.0.get(); @@ -1131,7 +1570,7 @@ mod store .extend(std::iter::repeat_with(|| None).take(missing)); } - self.store[index].get_or_insert_with(func) + self.store[index] = Some(asset); } pub fn reserve_index(&self) -> ReservedIndex -- cgit v1.2.3-18-g5258