summaryrefslogtreecommitdiff
path: root/engine/src
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2026-10-10 16:51:54 +0200
committerHampusM <hampus@hampusmat.com>2026-10-25 23:32:29 +0100
commit885f025ae2310d5e9b3de641224806fa1e996886 (patch)
treea59f813390052f6a2d2806d1ab87311af12118e4 /engine/src
parent54a204402de69408e47addc60e11c5a1daed819c (diff)
feat(engine): add asset failure state
Diffstat (limited to 'engine/src')
-rw-r--r--engine/src/asset.rs1017
1 files changed, 728 insertions, 289 deletions
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<hashbrown::HashMap<LabelOwned, Metadata>>,
+ id_lut: RefCell<hashbrown::HashMap<LabelOwned, IdValid>>,
+ metadata_lut: RefCell<hashbrown::HashMap<IdValid, AssetMetadata>>,
importers: hashbrown::HashMap<AssetTypeId, WrappedImporterFn>,
import_work_thread_pool: ThreadPool,
import_work_msg_receiver: MpscReceiver<ImportWorkMessage>,
@@ -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<AssetT, AssetSettings, Err>(
&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::<AssetT>())
+ fields(asset_type = %type_name::<AssetT>())
)]
pub fn get<AssetT>(&self, handle: &Handle<AssetT>) -> 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::<AssetT>() 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::<AssetT>() 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::<AssetT>())
+ fields(asset_type = %type_name::<AssetT>())
)]
pub fn get_mut<AssetT>(&mut self, handle: &Handle<AssetT>) -> 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::<AssetT>() 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::<AssetT>() else {
- tracing::error!("Wrong asset type");
return None;
};
Some(asset)
}
- #[tracing::instrument(skip_all, fields(label, asset_type = %type_name::<AssetT>()))]
+ #[tracing::instrument(skip_all, fields(asset_type = %type_name::<AssetT>()))]
pub fn get_handle_to_loaded<'label, AssetT>(
&self,
label: impl Into<Label<'label>>,
@@ -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::<AssetT>().is_none() {
- tracing::error!("Wrong asset type");
+ if !self.get_is_asset_ready(id) {
+ return None;
+ }
+
+ if stored_asset.data.downcast_ref::<AssetT>().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::<AssetT>().is_some()
+ if !self.get_is_asset_ready(id) {
+ return false;
+ }
+
+ stored_asset.data.downcast_ref::<AssetT>().is_some()
}
pub fn get_label<AssetT>(&self, handle: &Handle<AssetT>) -> Option<Label<'static>>
@@ -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::<AssetT>()))]
@@ -288,7 +416,8 @@ impl Assets
tracing::Span::current().record("label", tracing::field::display(&label));
- let id = self.load_asset::<Infallible>(AssetTypeId::of::<AssetT>(), label, None);
+ let (id, _res) =
+ self.load_asset::<Infallible>(AssetTypeId::of::<AssetT>(), 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::<AssetT>(), 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::<Infallible>(
- 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::<Infallible>(
+ 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::<usize>::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<IdValid>)
+ fn recurse_make_dependants_state_ready(&mut self, initial_dependants: Vec<IdValid>)
{
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::<usize>::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<IdValid, AssetMetadata>,
+ initial_dependants: Vec<IdValid>,
+ )
+ {
+ 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<AssetSettings>,
- ) -> 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::<AssetSettings>(asset_ty_id, &label, asset_settings);
+ tracing::debug!("Adding import work!");
- id
+ self.add_import_work::<AssetSettings>(
+ label.path.clone().into_owned(),
+ asset_settings,
+ importer,
+ );
+
+ (id, Ok(()))
}
fn add_import_work<AssetSettings>(
&self,
- asset_ty_id: AssetTypeId,
- label: &Label<'_>,
+ asset_path: PathBuf,
asset_settings: Option<AssetSettings>,
+ 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::<String>() {
- 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<ImportWorkMessage>,
- import_work_msg_sender: MpscSender<ImportWorkMessage>,
- asset_path: &'path Path,
+ storer_funcs: Vec<StorerFunc>,
+ _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::<AssetT>(label.as_label(), asset);
- }),
- label,
- });
+ self.storer_funcs.push(Box::new(move |assets, path| {
+ assets.store_with_label::<AssetT>(
+ Label { path: path.into(), name: None },
+ asset,
+ );
+ }));
}
pub fn submit_store_named<AssetT>(&mut self, name: impl AsRef<str>, 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::<AssetT>(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::<AssetT>(
+ 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<dyn std::error::Error + Send + Sync>),
+ ErrorResult(Box<dyn std::error::Error + Send + Sync>),
+
+ #[error("Thread panicked: {0}")]
+ ThreadPanicked(Cow<'static, str>),
}
#[derive(Debug, Clone)]
struct WrappedImporterFn
{
wrapper_func: fn(
- MpscSender<ImportWorkMessage>,
+ &MpscSender<ImportWorkMessage>,
&Path,
Option<&(dyn Any + Send + Sync)>,
) -> Result<(), ImporterError>,
@@ -854,15 +1234,15 @@ impl WrappedImporterFn
{
fn new<InnerFunc, AssetSettings, Err>(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::<InnerFunc>(), 0);
let wrapper_func =
- |import_work_msg_sender: MpscSender<ImportWorkMessage>,
+ |import_work_msg_sender: &MpscSender<ImportWorkMessage>,
asset_path: &Path,
asset_settings: Option<&(dyn Any + Send + Sync)>| {
let inner_func = unsafe { std::mem::zeroed::<InnerFunc>() };
@@ -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::<String>() {
+ 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<ImportWorkMessage>,
+ import_work_msg_sender: &MpscSender<ImportWorkMessage>,
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<Assets>, mut events: Single<Events>)
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<dyn FnOnce(&mut Assets, &Path) + Send>;
+
enum ImportWorkMessage
{
- Store
+ Completed
{
- do_store: Box<dyn FnOnce(&mut Assets, LabelOwned) + Send>,
- label: LabelOwned,
+ storer_funcs: Vec<StorerFunc>,
+ path: PathBuf,
},
+ Failed(PathBuf),
}
#[derive(Debug)]
-struct Metadata
+struct AssetMetadata
{
- id: IdValid,
- parent_id: Option<IdValid>,
+ label: LabelOwned,
+ waiting_dependants: Vec<IdValid>,
+ relation: AssetRelation,
+ state: AssetState,
}
#[derive(Debug)]
-enum AssetState
+enum AssetRelation
{
- Ready
+ Parent
{
- data: Box<dyn Any + Send + Sync>,
+ children: Vec<IdValid>
},
- Pending,
+ Child
+ {
+ parent: IdValid
+ },
+}
+
+#[derive(Debug)]
+enum AssetState
+{
+ Ready,
+ Loading,
DependenciesPending
{
- data: Box<dyn Any + Send + Sync>,
+ remaining_dependencies: NonZero<usize>,
+ },
+ Failed
+ {
+ reason: FailureReason,
remaining_dependencies: usize,
},
+ NotStored,
}
-#[derive(Debug)]
struct StoredAsset
{
- state: AssetState,
- label: LabelOwned,
- waiting_dependants: Vec<IdValid>,
+ data: Box<dyn Any + Send + Sync>,
}
#[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<Option<StoredAsset>>,
@@ -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