diff options
| author | HampusM <hampus@hampusmat.com> | 2026-10-09 12:24:23 +0200 |
|---|---|---|
| committer | HampusM <hampus@hampusmat.com> | 2026-10-09 15:10:54 +0200 |
| commit | 59b4f377ec0175eb21757637347c2689b8a1012f (patch) | |
| tree | 61567c1b6760da25c2672e7650ff7d87d3431986 /engine/src | |
| parent | 923a113c66ac1b505990a9c35895b5f4ca903390 (diff) | |
refactor(engine): remove need to hash content of asset IDs
Diffstat (limited to 'engine/src')
| -rw-r--r-- | engine/src/asset.rs | 538 | ||||
| -rw-r--r-- | engine/src/model/asset.rs | 1 |
2 files changed, 300 insertions, 239 deletions
diff --git a/engine/src/asset.rs b/engine/src/asset.rs index afa0ea4..8f7829c 100644 --- a/engine/src/asset.rs +++ b/engine/src/asset.rs @@ -1,10 +1,9 @@ use std::any::{type_name, Any, TypeId}; use std::borrow::Cow; use std::cell::RefCell; -use std::collections::HashMap; use std::convert::Infallible; use std::fmt::{Debug, Display}; -use std::hash::{DefaultHasher, Hash, Hasher}; +use std::hash::Hash; use std::hint::cold_path; use std::marker::PhantomData; use std::path::{Path, PathBuf}; @@ -104,8 +103,8 @@ impl Display for Label<'_> #[derive(Debug, Sole)] pub struct Assets { - store: Vec<StoredAsset>, - asset_lookup: RefCell<HashMap<LabelHash, LookupEntry>>, + store: store::Store, + metadata_lut: RefCell<hashbrown::HashMap<LabelOwned, Metadata>>, importers: hashbrown::HashMap<AssetTypeId, WrappedImporterFn>, import_work_queue: WorkQueue<ImportWorkUserData>, import_work_msg_receiver: MpscReceiver<ImportWorkMessage>, @@ -122,8 +121,8 @@ impl Assets mpsc_channel::<ImportWorkMessage>(); Self { - store: Vec::with_capacity(capacity), - asset_lookup: RefCell::new(HashMap::with_capacity(capacity)), + store: store::Store::with_capacity(capacity), + metadata_lut: RefCell::new(hashbrown::HashMap::with_capacity(capacity)), importers: hashbrown::HashMap::new(), import_work_queue: WorkQueue::new("asset_importing_work_queue"), import_work_msg_receiver, @@ -154,22 +153,16 @@ impl Assets { let id = handle.id.into_valid()?; - let asset_lookup = self.asset_lookup.borrow(); - - let AssetState::Ready(asset_index, asset_label) = - &asset_lookup.get(&id.label_hash)?.state - else { - return None; - }; + let stored_asset = self.store.get(id.index)?; tracing::Span::current() - .record("asset_label", tracing::field::display(&asset_label)); + .record("asset_label", tracing::field::display(&stored_asset.label)); - let Some(stored_asset) = self.store.get(*asset_index) else { - unreachable!(); + let AssetState::Ready { data: asset_data } = &stored_asset.state else { + return None; }; - let Some(asset) = stored_asset.inner.downcast_ref::<AssetT>() else { + let Some(asset) = asset_data.downcast_ref::<AssetT>() else { tracing::error!("Wrong asset type"); return None; }; @@ -187,22 +180,16 @@ impl Assets { let id = handle.id.into_valid()?; - let asset_lookup = self.asset_lookup.borrow(); - - let AssetState::Ready(asset_index, asset_label) = - &asset_lookup.get(&id.label_hash)?.state - else { - return None; - }; + let stored_asset = self.store.get_mut(id.index)?; tracing::Span::current() - .record("asset_label", tracing::field::display(&asset_label)); + .record("asset_label", tracing::field::display(&stored_asset.label)); - let Some(stored_asset) = self.store.get_mut(*asset_index) else { - unreachable!(); + let AssetState::Ready { data: asset_data } = &mut stored_asset.state else { + return None; }; - let Some(asset) = stored_asset.inner.downcast_mut::<AssetT>() else { + let Some(asset) = asset_data.downcast_mut::<AssetT>() else { tracing::error!("Wrong asset type"); return None; }; @@ -222,25 +209,22 @@ impl Assets tracing::Span::current().record("label", tracing::field::display(&label)); - let label_hash = LabelHash::new(&label); - - let asset_lookup = self.asset_lookup.borrow(); + let (stored_asset, id) = self.get_asset_by_label(&label)?; - let AssetState::Ready(asset_index, _) = &asset_lookup.get(&label_hash)?.state + let Some(StoredAsset { + state: AssetState::Ready { data: asset_data }, + .. + }) = stored_asset else { return None; }; - let Some(stored_asset) = self.store.get(*asset_index) else { - unreachable!(); - }; - - if stored_asset.inner.downcast_ref::<AssetT>().is_none() { + if asset_data.downcast_ref::<AssetT>().is_none() { tracing::error!("Wrong asset type"); return None; } - Some(Handle::new(label_hash)) + Some(Handle::from_id(Id::from_valid(id))) } pub fn is_loaded_and_has_type<AssetT>(&self, handle: &Handle<AssetT>) -> bool @@ -251,19 +235,15 @@ impl Assets return false; }; - let asset_lookup = self.asset_lookup.borrow(); - - let Some(AssetState::Ready(asset_index, _)) = - asset_lookup.get(&id.label_hash).map(|entry| &entry.state) + let Some(StoredAsset { + state: AssetState::Ready { data: asset_data }, + .. + }) = self.store.get(id.index) else { return false; }; - let Some(stored_asset) = self.store.get(*asset_index) else { - unreachable!(); - }; - - stored_asset.inner.downcast_ref::<AssetT>().is_some() + asset_data.downcast_ref::<AssetT>().is_some() } pub fn get_label<AssetT>(&self, handle: &Handle<AssetT>) -> Option<Label<'static>> @@ -277,13 +257,9 @@ impl Assets { let id = id.into_valid()?; - let asset_lookup = self.asset_lookup.borrow(); + let stored_asset = self.store.get(id.index)?; - let AssetState::Ready(_, label) = &asset_lookup.get(&id.label_hash)?.state else { - return None; - }; - - Some(label.clone().into_label()) + Some(stored_asset.label.clone().into_label()) } #[tracing::instrument(skip_all, fields(label, asset_type = %type_name::<AssetT>()))] @@ -295,16 +271,9 @@ impl Assets tracing::Span::current().record("label", tracing::field::display(&label)); - let label_hash = LabelHash::new(&label); - - self.load_asset::<Infallible>( - AssetTypeId::of::<AssetT>(), - label, - label_hash, - None, - ); + let id = self.load_asset::<Infallible>(AssetTypeId::of::<AssetT>(), label, None); - Handle::new(label_hash) + Handle::from_id(Id::from_valid(id)) } #[tracing::instrument(skip_all, fields(label, asset_type = %type_name::<AssetT>()))] @@ -321,16 +290,10 @@ impl Assets tracing::Span::current().record("label", tracing::field::display(&label)); - let label_hash = LabelHash::new(&label); + let id = + self.load_asset(AssetTypeId::of::<AssetT>(), label, Some(asset_settings)); - self.load_asset( - AssetTypeId::of::<AssetT>(), - label, - label_hash, - Some(asset_settings), - ); - - Handle::new(label_hash) + Handle::from_id(Id::from_valid(id)) } pub fn store_with_name<'name, AssetT>( @@ -382,58 +345,64 @@ impl Assets tracing::Span::current().record("label", tracing::field::display(&label)); - let label_hash = LabelHash::new(&label); + let id = match self.get_asset_by_label(&label) { + Some(( + Some(StoredAsset { + state: + AssetState::Ready { .. } | AssetState::DependenciesPending { .. }, + .. + }), + id, + )) => { + tracing::error!("Asset already exists"); - let id = Id { - inner: IdInner::Valid(IdValid { label_hash }), + return Handle::from_id(Id::from_valid(id)); + } + Some((Some(StoredAsset { state: AssetState::Pending, .. }) | None, id)) => id, + None => IdValid::new(&self.store, &label), }; - if matches!( - self.asset_lookup - .get_mut() - .get(&label_hash) - .map(|entry| &entry.state), - Some(AssetState::Ready(..) | AssetState::DependenciesPending { .. }) - ) { - tracing::error!("Asset already exists"); - - return Handle::new(label_hash); - } - let mut missing_dependencies = 0; for dependency in asset.dependencies() { - match self - .asset_lookup - .get_mut() - .get_mut(&LabelHash::new(&dependency.label)) - { - Some(LookupEntry { state: AssetState::Ready(..), .. }) => {} - Some(LookupEntry { - state: AssetState::Pending | AssetState::DependenciesPending { .. }, - waiting_dependants, - }) => { + 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); } - None => { - missing_dependencies += 1; + Some((None, _)) | None => { + tracing::trace!( + dependency = %dependency.label, + "Asset has missing dependency" + ); - let dependency_label_hash = LabelHash::new(&dependency.label); + missing_dependencies += 1; - self.load_asset::<Infallible>( + let dependency_id = self.load_asset::<Infallible>( dependency.asset_ty_id, dependency.label.clone(), - dependency_label_hash, None, ); - self.asset_lookup - .get_mut() - .entry(LabelHash::new(&dependency.label)) - .or_insert_with(|| LookupEntry { + self.store + .get_or_insert_with(dependency_id.index, || StoredAsset { state: AssetState::Pending, + label: dependency.label.to_owned(), waiting_dependants: Vec::new(), }) .waiting_dependants @@ -444,42 +413,55 @@ impl Assets tracing::debug!("Storing asset"); - self.store.push(StoredAsset::new(asset)); - - let index = self.store.len() - 1; + let stored_asset = self.store.get_or_insert_with(id.index, || StoredAsset { + state: AssetState::Pending, + label: label.to_owned(), + waiting_dependants: Vec::new(), + }); let label = label.to_owned(); - let entry = self - .asset_lookup - .get_mut() - .entry(label_hash) - .or_insert_with(|| LookupEntry { - state: AssetState::Pending, - waiting_dependants: Vec::new(), - }); - if missing_dependencies != 0 { - entry.state = AssetState::DependenciesPending { - index, - label: label.clone(), + stored_asset.state = AssetState::DependenciesPending { + data: Box::new(asset), remaining_dependencies: missing_dependencies, }; } else { - entry.state = AssetState::Ready(index, label.clone()); + stored_asset.state = AssetState::Ready { data: Box::new(asset) }; - self.curr_tick_events - .push(Event::Stored(id, label.into_label())); + self.curr_tick_events.push(Event::Stored( + Id::from_valid(id), + label.clone().into_label(), + )); - let waiting_dependants = std::mem::take(&mut entry.waiting_dependants); + let waiting_dependants = std::mem::take(&mut stored_asset.waiting_dependants); self.recurse_update_dependants(waiting_dependants); } - Handle::from_id(id) + let metadata_lut = self.metadata_lut.get_mut(); + + let parent_id = label.name.is_some().then(|| { + let parent_label = Label { + path: label.path.as_path().into(), + name: None, + }; + + metadata_lut + .entry(parent_label.to_owned()) + .or_insert_with(|| Metadata { + id: IdValid::new(&self.store, &parent_label), + parent_id: None, + }) + .id + }); + + metadata_lut.insert(label.clone(), Metadata { id, parent_id }); + + Handle::from_id(Id::from_valid(id)) } - fn recurse_update_dependants(&mut self, initial_dependants: Vec<Id>) + fn recurse_update_dependants(&mut self, initial_dependants: Vec<IdValid>) { let mut all_dependants = initial_dependants; @@ -491,18 +473,14 @@ impl Assets } for dependant_id in dependants { - let dependant_label_hash = dependant_id.into_valid().unwrap().label_hash; - - let Some(LookupEntry { state, waiting_dependants }) = - self.asset_lookup.get_mut().get_mut(&dependant_label_hash) - else { + let Some(dependant_asset) = self.store.get_mut(dependant_id.index) else { continue; }; { let AssetState::DependenciesPending { remaining_dependencies, .. - } = state + } = &mut dependant_asset.state else { continue; }; @@ -514,79 +492,78 @@ impl Assets } } - let AssetState::DependenciesPending { index, label, .. } = - std::mem::replace(state, AssetState::Pending) + let AssetState::DependenciesPending { data: dependant_data, .. } = + std::mem::replace(&mut dependant_asset.state, AssetState::Pending) else { unreachable!(); }; - *state = AssetState::Ready(index, label.clone()); + dependant_asset.state = AssetState::Ready { data: dependant_data }; - self.curr_tick_events - .push(Event::Stored(dependant_id, label.into_label())); + self.curr_tick_events.push(Event::Stored( + Id::from_valid(dependant_id), + dependant_asset.label.clone().into_label(), + )); - all_dependants.append(waiting_dependants); + all_dependants.append(&mut dependant_asset.waiting_dependants); } } } - fn is_pending(asset_lookup: &HashMap<LabelHash, LookupEntry>, label: &Label) -> bool - { - if label.name.is_some() { - if let Some(AssetState::Pending | AssetState::DependenciesPending { .. }) = - asset_lookup - .get(&LabelHash::new(&Label { - path: label.path.as_ref().into(), - name: None, - })) - .map(|entry| &entry.state) - { - return true; - } - } - - if let Some(AssetState::Pending | AssetState::DependenciesPending { .. }) = - asset_lookup - .get(&LabelHash::new(label)) - .map(|entry| &entry.state) - { - return true; - } - - false - } - fn load_asset<AssetSettings>( &self, asset_ty_id: AssetTypeId, label: Label<'_>, - label_hash: LabelHash, asset_settings: Option<AssetSettings>, - ) where + ) -> IdValid + where AssetSettings: Send + Sync + Debug + 'static, { - let mut asset_lookup = self.asset_lookup.borrow_mut(); + let id = match self.get_asset_by_label(&label) { + Some((Some(_), id)) => { + return id; + } + Some((None, id)) => id, + None => IdValid::new(&self.store, &label), + }; - if Self::is_pending(&asset_lookup, &label) { - return; + let mut metadata_lut = self.metadata_lut.borrow_mut(); + + let mut parent_exists = false; + + let parent_id = label.name.is_some().then(|| { + let parent_label = Label { + path: label.path.as_ref().into(), + name: None, + }; + + parent_exists = metadata_lut.contains_key(&parent_label); + + metadata_lut + .entry(parent_label.to_owned()) + .or_insert_with(|| Metadata { + id: IdValid::new(&self.store, &parent_label), + parent_id: None, + }) + .id + }); + + metadata_lut.insert(label.to_owned(), Metadata { id, parent_id }); + + if parent_exists { + return id; } - self.add_import_work::<AssetSettings>( - asset_ty_id, - &label, - label_hash, - asset_settings, - &mut asset_lookup, - ); + self.add_import_work::<AssetSettings>(asset_ty_id, &label, asset_settings); + + id } fn add_import_work<AssetSettings>( &self, asset_ty_id: AssetTypeId, label: &Label<'_>, - label_hash: LabelHash, asset_settings: Option<AssetSettings>, - asset_lookup: &mut HashMap<LabelHash, LookupEntry>, ) where AssetSettings: Any + Send + Sync, { @@ -623,33 +600,30 @@ impl Assets importer: importer.clone(), }, }); + } + + fn get_asset_by_label( + &self, + label: &Label<'_>, + ) -> Option<(Option<&StoredAsset>, IdValid)> + { + let metadata_lut = self.metadata_lut.borrow(); - assert!(!asset_lookup.contains_key(&label_hash)); + let id = metadata_lut.get(label)?.id; - asset_lookup.insert( - label_hash, - LookupEntry { - state: AssetState::Pending, - waiting_dependants: Vec::new(), - }, - ); + Some((self.store.get(id.index), id)) + } - if label.name.is_some() { - let parent_label_hash = LabelHash::new(&Label { - path: label.path.as_ref().into(), - name: None, - }); + fn get_asset_by_label_mut( + &mut self, + label: &Label<'_>, + ) -> Option<(Option<&mut StoredAsset>, IdValid)> + { + let metadata_lut = self.metadata_lut.get_mut(); - assert!(!asset_lookup.contains_key(&parent_label_hash)); + let id = metadata_lut.get(label)?.id; - asset_lookup.insert( - parent_label_hash, - LookupEntry { - state: AssetState::Pending, - waiting_dependants: Vec::new(), - }, - ); - } + Some((self.store.get_mut(id.index), id)) } } @@ -724,6 +698,7 @@ pub struct Handle<AssetT: Asset> impl<AssetT: Asset> Handle<AssetT> { + #[must_use] pub fn invalid() -> Self { Self::from_id(Id::invalid()) @@ -740,13 +715,6 @@ impl<AssetT: Asset> Handle<AssetT> { self.id } - - fn new(label_hash: LabelHash) -> Self - { - Self::from_id(Id { - inner: IdInner::Valid(IdValid { label_hash }), - }) - } } impl<AssetT: Asset> Clone for Handle<AssetT> @@ -766,6 +734,7 @@ pub struct Id impl Id { + #[must_use] pub fn invalid() -> Self { Self { inner: IdInner::Invalid } @@ -778,6 +747,11 @@ impl Id IdInner::Invalid => None, } } + + fn from_valid(id: IdValid) -> Self + { + Self { inner: IdInner::Valid(id) } + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -790,7 +764,19 @@ enum IdInner #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] struct IdValid { - label_hash: LabelHash, + 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)] @@ -890,21 +876,6 @@ impl WrappedImporterFn } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -struct LabelHash(u64); - -impl LabelHash -{ - fn new(label: &Label<'_>) -> Self - { - let mut hasher = DefaultHasher::new(); - - label.hash(&mut hasher); - - Self(hasher.finish()) - } -} - #[derive(Debug, Default)] pub(crate) struct Extension { @@ -982,21 +953,23 @@ enum ImportWorkMessage } #[derive(Debug)] -struct LookupEntry +struct Metadata { - state: AssetState, - waiting_dependants: Vec<Id>, + id: IdValid, + parent_id: Option<IdValid>, } #[derive(Debug)] enum AssetState { - Ready(usize, LabelOwned), + Ready + { + data: Box<dyn Any + Send + Sync>, + }, Pending, DependenciesPending { - index: usize, - label: LabelOwned, + data: Box<dyn Any + Send + Sync>, remaining_dependencies: usize, }, } @@ -1004,15 +977,9 @@ enum AssetState #[derive(Debug)] struct StoredAsset { - inner: Box<dyn Any + Send + Sync>, -} - -impl StoredAsset -{ - fn new<AssetT: Asset>(asset: AssetT) -> Self - { - Self { inner: Box::new(asset) } - } + state: AssetState, + label: LabelOwned, + waiting_dependants: Vec<IdValid>, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -1029,7 +996,7 @@ impl AssetTypeId } } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Eq)] struct LabelOwned { path: PathBuf, @@ -1063,3 +1030,96 @@ impl Display for LabelOwned <Label<'_> 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<HasherT: std::hash::Hasher>(&self, state: &mut HasherT) + { + <Label<'_> as Hash>::hash(&self.as_label(), state); + } +} + +impl hashbrown::Equivalent<LabelOwned> for Label<'_> +{ + fn equivalent(&self, key: &LabelOwned) -> bool + { + self == &key.as_label() + } +} + +mod store +{ + use std::num::NonZero; + use std::sync::atomic::{AtomicUsize, Ordering}; + + use super::StoredAsset; + + #[derive(Debug)] + pub struct Store + { + store: Vec<Option<StoredAsset>>, + 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 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 get_or_insert_with( + &mut self, + index: ReservedIndex, + func: impl FnOnce() -> StoredAsset, + ) -> &mut 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].get_or_insert_with(func) + } + + pub fn reserve_index(&self) -> ReservedIndex + { + let index = self.next_index.fetch_add(1, Ordering::Relaxed); + + let index = NonZero::<usize>::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<usize>); +} diff --git a/engine/src/model/asset.rs b/engine/src/model/asset.rs index d9de6ce..b000d7e 100644 --- a/engine/src/model/asset.rs +++ b/engine/src/model/asset.rs @@ -47,6 +47,7 @@ pub fn add_importers(assets: &mut Assets) { assets.set_importer::<Model, Settings, Error>(import); assets.set_importer::<Mesh, Settings, Error>(import); + assets.set_importer::<MaterialDescription, Settings, Error>(import); } fn import( |
