1mod info;
2mod loaders;
3
4use crate::{
5 folder::LoadedFolder,
6 io::{
7 AssetReaderError, AssetSource, AssetSourceEvent, AssetSourceId, AssetSources,
8 ErasedAssetReader, MissingAssetSourceError, MissingProcessedAssetReaderError, Reader,
9 },
10 loader::{AssetLoader, ErasedAssetLoader, LoadContext, LoadedAsset},
11 meta::{
12 loader_settings_meta_transform, AssetActionMinimal, AssetMetaDyn, AssetMetaMinimal,
13 MetaTransform, Settings,
14 },
15 path::AssetPath,
16 Asset, AssetEvent, AssetHandleProvider, AssetId, AssetLoadFailedEvent, AssetMetaCheck, Assets,
17 DeserializeMetaError, ErasedLoadedAsset, Handle, LoadedUntypedAsset, UntypedAssetId,
18 UntypedAssetLoadFailedEvent, UntypedHandle,
19};
20use bevy_ecs::prelude::*;
21use bevy_tasks::IoTaskPool;
22use bevy_utils::tracing::{error, info};
23use bevy_utils::{CowArc, HashSet};
24use crossbeam_channel::{Receiver, Sender};
25use futures_lite::StreamExt;
26use info::*;
27use loaders::*;
28use parking_lot::RwLock;
29use std::future::Future;
30use std::{any::Any, path::PathBuf};
31use std::{any::TypeId, path::Path, sync::Arc};
32use thiserror::Error;
33
34#[allow(unused_imports)]
36use crate::io::{AssetReader, AssetWriter};
37
38#[derive(Resource, Clone)]
53pub struct AssetServer {
54 pub(crate) data: Arc<AssetServerData>,
55}
56
57pub(crate) struct AssetServerData {
59 pub(crate) infos: RwLock<AssetInfos>,
60 pub(crate) loaders: Arc<RwLock<AssetLoaders>>,
61 asset_event_sender: Sender<InternalAssetEvent>,
62 asset_event_receiver: Receiver<InternalAssetEvent>,
63 sources: AssetSources,
64 mode: AssetServerMode,
65 meta_check: AssetMetaCheck,
66}
67
68#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70pub enum AssetServerMode {
71 Unprocessed,
73 Processed,
75}
76
77impl AssetServer {
78 pub fn new(sources: AssetSources, mode: AssetServerMode, watching_for_changes: bool) -> Self {
81 Self::new_with_loaders(
82 sources,
83 Default::default(),
84 mode,
85 AssetMetaCheck::Always,
86 watching_for_changes,
87 )
88 }
89
90 pub fn new_with_meta_check(
93 sources: AssetSources,
94 mode: AssetServerMode,
95 meta_check: AssetMetaCheck,
96 watching_for_changes: bool,
97 ) -> Self {
98 Self::new_with_loaders(
99 sources,
100 Default::default(),
101 mode,
102 meta_check,
103 watching_for_changes,
104 )
105 }
106
107 pub(crate) fn new_with_loaders(
108 sources: AssetSources,
109 loaders: Arc<RwLock<AssetLoaders>>,
110 mode: AssetServerMode,
111 meta_check: AssetMetaCheck,
112 watching_for_changes: bool,
113 ) -> Self {
114 let (asset_event_sender, asset_event_receiver) = crossbeam_channel::unbounded();
115 let mut infos = AssetInfos::default();
116 infos.watching_for_changes = watching_for_changes;
117 Self {
118 data: Arc::new(AssetServerData {
119 sources,
120 mode,
121 meta_check,
122 asset_event_sender,
123 asset_event_receiver,
124 loaders,
125 infos: RwLock::new(infos),
126 }),
127 }
128 }
129
130 pub fn get_source<'a>(
132 &'a self,
133 source: impl Into<AssetSourceId<'a>>,
134 ) -> Result<&'a AssetSource, MissingAssetSourceError> {
135 self.data.sources.get(source.into())
136 }
137
138 pub fn watching_for_changes(&self) -> bool {
140 self.data.infos.read().watching_for_changes
141 }
142
143 pub fn register_loader<L: AssetLoader>(&self, loader: L) {
145 self.data.loaders.write().push(loader);
146 }
147
148 pub fn register_asset<A: Asset>(&self, assets: &Assets<A>) {
150 self.register_handle_provider(assets.get_handle_provider());
151 fn sender<A: Asset>(world: &mut World, id: UntypedAssetId) {
152 world
153 .resource_mut::<Events<AssetEvent<A>>>()
154 .send(AssetEvent::LoadedWithDependencies { id: id.typed() });
155 }
156 fn failed_sender<A: Asset>(
157 world: &mut World,
158 id: UntypedAssetId,
159 path: AssetPath<'static>,
160 error: AssetLoadError,
161 ) {
162 world
163 .resource_mut::<Events<AssetLoadFailedEvent<A>>>()
164 .send(AssetLoadFailedEvent {
165 id: id.typed(),
166 path,
167 error,
168 });
169 }
170
171 let mut infos = self.data.infos.write();
172
173 infos
174 .dependency_loaded_event_sender
175 .insert(TypeId::of::<A>(), sender::<A>);
176
177 infos
178 .dependency_failed_event_sender
179 .insert(TypeId::of::<A>(), failed_sender::<A>);
180 }
181
182 pub(crate) fn register_handle_provider(&self, handle_provider: AssetHandleProvider) {
183 let mut infos = self.data.infos.write();
184 infos
185 .handle_providers
186 .insert(handle_provider.type_id, handle_provider);
187 }
188
189 pub async fn get_asset_loader_with_extension(
191 &self,
192 extension: &str,
193 ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForExtensionError> {
194 let error = || MissingAssetLoaderForExtensionError {
195 extensions: vec![extension.to_string()],
196 };
197
198 let loader = { self.data.loaders.read().get_by_extension(extension) };
199
200 loader.ok_or_else(error)?.get().await.map_err(|_| error())
201 }
202
203 pub async fn get_asset_loader_with_type_name(
205 &self,
206 type_name: &str,
207 ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForTypeNameError> {
208 let error = || MissingAssetLoaderForTypeNameError {
209 type_name: type_name.to_string(),
210 };
211
212 let loader = { self.data.loaders.read().get_by_name(type_name) };
213
214 loader.ok_or_else(error)?.get().await.map_err(|_| error())
215 }
216
217 pub async fn get_path_asset_loader<'a>(
219 &self,
220 path: impl Into<AssetPath<'a>>,
221 ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForExtensionError> {
222 let path = path.into();
223
224 let error = || {
225 let Some(full_extension) = path.get_full_extension() else {
226 return MissingAssetLoaderForExtensionError {
227 extensions: Vec::new(),
228 };
229 };
230
231 let mut extensions = vec![full_extension.clone()];
232 extensions.extend(
233 AssetPath::iter_secondary_extensions(&full_extension).map(|e| e.to_string()),
234 );
235
236 MissingAssetLoaderForExtensionError { extensions }
237 };
238
239 let loader = { self.data.loaders.read().get_by_path(&path) };
240
241 loader.ok_or_else(error)?.get().await.map_err(|_| error())
242 }
243
244 pub async fn get_asset_loader_with_asset_type_id<'a>(
246 &self,
247 type_id: TypeId,
248 ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForTypeIdError> {
249 let error = || MissingAssetLoaderForTypeIdError { type_id };
250
251 let loader = { self.data.loaders.read().get_by_type(type_id) };
252
253 loader.ok_or_else(error)?.get().await.map_err(|_| error())
254 }
255
256 pub async fn get_asset_loader_with_asset_type<'a, A: Asset>(
258 &self,
259 ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForTypeIdError> {
260 self.get_asset_loader_with_asset_type_id(TypeId::of::<A>())
261 .await
262 }
263
264 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
305 pub fn load<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Handle<A> {
306 self.load_with_meta_transform(path, None, ())
307 }
308
309 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
325 pub fn load_acquire<'a, A: Asset, G: Send + Sync + 'static>(
326 &self,
327 path: impl Into<AssetPath<'a>>,
328 guard: G,
329 ) -> Handle<A> {
330 self.load_with_meta_transform(path, None, guard)
331 }
332
333 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
337 pub fn load_with_settings<'a, A: Asset, S: Settings>(
338 &self,
339 path: impl Into<AssetPath<'a>>,
340 settings: impl Fn(&mut S) + Send + Sync + 'static,
341 ) -> Handle<A> {
342 self.load_with_meta_transform(path, Some(loader_settings_meta_transform(settings)), ())
343 }
344
345 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
355 pub fn load_acquire_with_settings<'a, A: Asset, S: Settings, G: Send + Sync + 'static>(
356 &self,
357 path: impl Into<AssetPath<'a>>,
358 settings: impl Fn(&mut S) + Send + Sync + 'static,
359 guard: G,
360 ) -> Handle<A> {
361 self.load_with_meta_transform(path, Some(loader_settings_meta_transform(settings)), guard)
362 }
363
364 pub(crate) fn load_with_meta_transform<'a, A: Asset, G: Send + Sync + 'static>(
365 &self,
366 path: impl Into<AssetPath<'a>>,
367 meta_transform: Option<MetaTransform>,
368 guard: G,
369 ) -> Handle<A> {
370 let path = path.into().into_owned();
371 let (handle, should_load) = self.data.infos.write().get_or_create_path_handle::<A>(
372 path.clone(),
373 HandleLoadingMode::Request,
374 meta_transform,
375 );
376
377 if should_load {
378 let owned_handle = Some(handle.clone().untyped());
379 let server = self.clone();
380 IoTaskPool::get()
381 .spawn(async move {
382 if let Err(err) = server.load_internal(owned_handle, path, false, None).await {
383 error!("{}", err);
384 }
385 drop(guard);
386 })
387 .detach();
388 }
389
390 handle
391 }
392
393 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
397 pub async fn load_untyped_async<'a>(
398 &self,
399 path: impl Into<AssetPath<'a>>,
400 ) -> Result<UntypedHandle, AssetLoadError> {
401 let path: AssetPath = path.into();
402 self.load_internal(None, path, false, None).await
403 }
404
405 pub(crate) fn load_untyped_with_meta_transform<'a>(
406 &self,
407 path: impl Into<AssetPath<'a>>,
408 meta_transform: Option<MetaTransform>,
409 ) -> Handle<LoadedUntypedAsset> {
410 let path = path.into().into_owned();
411 let untyped_source = AssetSourceId::Name(match path.source() {
412 AssetSourceId::Default => CowArc::Borrowed(UNTYPED_SOURCE_SUFFIX),
413 AssetSourceId::Name(source) => {
414 CowArc::Owned(format!("{source}--{UNTYPED_SOURCE_SUFFIX}").into())
415 }
416 });
417 let (handle, should_load) = self
418 .data
419 .infos
420 .write()
421 .get_or_create_path_handle::<LoadedUntypedAsset>(
422 path.clone().with_source(untyped_source),
423 HandleLoadingMode::Request,
424 meta_transform,
425 );
426 if !should_load {
427 return handle;
428 }
429 let id = handle.id().untyped();
430
431 let server = self.clone();
432 IoTaskPool::get()
433 .spawn(async move {
434 let path_clone = path.clone();
435 match server.load_untyped_async(path).await {
436 Ok(handle) => server.send_asset_event(InternalAssetEvent::Loaded {
437 id,
438 loaded_asset: LoadedAsset::new_with_dependencies(
439 LoadedUntypedAsset { handle },
440 None,
441 )
442 .into(),
443 }),
444 Err(err) => {
445 error!("{err}");
446 server.send_asset_event(InternalAssetEvent::Failed {
447 id,
448 path: path_clone,
449 error: err,
450 });
451 }
452 }
453 })
454 .detach();
455 handle
456 }
457
458 #[must_use = "not using the returned strong handle may result in the unexpected release of the assets"]
481 pub fn load_untyped<'a>(&self, path: impl Into<AssetPath<'a>>) -> Handle<LoadedUntypedAsset> {
482 self.load_untyped_with_meta_transform(path, None)
483 }
484
485 async fn load_internal<'a>(
490 &self,
491 input_handle: Option<UntypedHandle>,
492 path: AssetPath<'a>,
493 force: bool,
494 meta_transform: Option<MetaTransform>,
495 ) -> Result<UntypedHandle, AssetLoadError> {
496 let asset_type_id = input_handle.as_ref().map(|handle| handle.type_id());
497
498 let path = path.into_owned();
499 let path_clone = path.clone();
500 let (mut meta, loader, mut reader) = self
501 .get_meta_loader_and_reader(&path_clone, asset_type_id)
502 .await
503 .map_err(|e| {
504 if let Some(handle) = &input_handle {
507 self.send_asset_event(InternalAssetEvent::Failed {
508 id: handle.id(),
509 path: path.clone_owned(),
510 error: e.clone(),
511 });
512 }
513 e
514 })?;
515
516 let handle_result = match input_handle {
530 Some(handle) => {
531 Some((handle, true))
533 }
534 None => {
535 let mut infos = self.data.infos.write();
536 let result = infos.get_or_create_path_handle_internal(
537 path.clone(),
538 path.label().is_none().then(|| loader.asset_type_id()),
539 HandleLoadingMode::Request,
540 meta_transform,
541 );
542 unwrap_with_context(result, loader.asset_type_name())
543 }
544 };
545
546 let handle = if let Some((handle, should_load)) = handle_result {
547 if path.label().is_none() && handle.type_id() != loader.asset_type_id() {
548 error!(
549 "Expected {:?}, got {:?}",
550 handle.type_id(),
551 loader.asset_type_id()
552 );
553 return Err(AssetLoadError::RequestedHandleTypeMismatch {
554 path: path.into_owned(),
555 requested: handle.type_id(),
556 actual_asset_name: loader.asset_type_name(),
557 loader_name: loader.type_name(),
558 });
559 }
560 if !should_load && !force {
561 return Ok(handle);
562 }
563 Some(handle)
564 } else {
565 None
566 };
567 let (base_handle, base_path) = if path.label().is_some() {
570 let mut infos = self.data.infos.write();
571 let base_path = path.without_label().into_owned();
572 let (base_handle, _) = infos.get_or_create_path_handle_untyped(
573 base_path.clone(),
574 loader.asset_type_id(),
575 loader.asset_type_name(),
576 HandleLoadingMode::Force,
577 None,
578 );
579 (base_handle, base_path)
580 } else {
581 (handle.clone().unwrap(), path.clone())
582 };
583
584 if let Some(meta_transform) = base_handle.meta_transform() {
585 (*meta_transform)(&mut *meta);
586 }
587
588 match self
589 .load_with_meta_loader_and_reader(&base_path, meta, &*loader, &mut *reader, true, false)
590 .await
591 {
592 Ok(loaded_asset) => {
593 let final_handle = if let Some(label) = path.label_cow() {
594 match loaded_asset.labeled_assets.get(&label) {
595 Some(labeled_asset) => labeled_asset.handle.clone(),
596 None => {
597 let mut all_labels: Vec<String> = loaded_asset
598 .labeled_assets
599 .keys()
600 .map(|s| (**s).to_owned())
601 .collect();
602 all_labels.sort_unstable();
603 return Err(AssetLoadError::MissingLabel {
604 base_path,
605 label: label.to_string(),
606 all_labels,
607 });
608 }
609 }
610 } else {
611 handle.unwrap()
613 };
614
615 self.send_loaded_asset(base_handle.id(), loaded_asset);
616 Ok(final_handle)
617 }
618 Err(err) => {
619 self.send_asset_event(InternalAssetEvent::Failed {
620 id: base_handle.id(),
621 error: err.clone(),
622 path: path.into_owned(),
623 });
624 Err(err)
625 }
626 }
627 }
628
629 fn send_loaded_asset(&self, id: UntypedAssetId, mut loaded_asset: ErasedLoadedAsset) {
632 for (_, labeled_asset) in loaded_asset.labeled_assets.drain() {
633 self.send_loaded_asset(labeled_asset.handle.id(), labeled_asset.asset);
634 }
635
636 self.send_asset_event(InternalAssetEvent::Loaded { id, loaded_asset });
637 }
638
639 pub fn reload<'a>(&self, path: impl Into<AssetPath<'a>>) {
641 let server = self.clone();
642 let path = path.into().into_owned();
643 IoTaskPool::get()
644 .spawn(async move {
645 let mut reloaded = false;
646
647 let requests = server
648 .data
649 .infos
650 .read()
651 .get_path_handles(&path)
652 .map(|handle| server.load_internal(Some(handle), path.clone(), true, None))
653 .collect::<Vec<_>>();
654
655 for result in requests {
656 match result.await {
657 Ok(_) => reloaded = true,
658 Err(err) => error!("{}", err),
659 }
660 }
661
662 if !reloaded && server.data.infos.read().should_reload(&path) {
663 if let Err(err) = server.load_internal(None, path, true, None).await {
664 error!("{}", err);
665 }
666 }
667 })
668 .detach();
669 }
670
671 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
676 pub fn add<A: Asset>(&self, asset: A) -> Handle<A> {
677 self.load_asset(LoadedAsset::new_with_dependencies(asset, None))
678 }
679
680 pub(crate) fn load_asset<A: Asset>(&self, asset: impl Into<LoadedAsset<A>>) -> Handle<A> {
681 let loaded_asset: LoadedAsset<A> = asset.into();
682 let erased_loaded_asset: ErasedLoadedAsset = loaded_asset.into();
683 self.load_asset_untyped(None, erased_loaded_asset)
684 .typed_debug_checked()
685 }
686
687 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
688 pub(crate) fn load_asset_untyped(
689 &self,
690 path: Option<AssetPath<'static>>,
691 asset: impl Into<ErasedLoadedAsset>,
692 ) -> UntypedHandle {
693 let loaded_asset = asset.into();
694 let handle = if let Some(path) = path {
695 let (handle, _) = self.data.infos.write().get_or_create_path_handle_untyped(
696 path,
697 loaded_asset.asset_type_id(),
698 loaded_asset.asset_type_name(),
699 HandleLoadingMode::NotLoading,
700 None,
701 );
702 handle
703 } else {
704 self.data.infos.write().create_loading_handle_untyped(
705 loaded_asset.asset_type_id(),
706 loaded_asset.asset_type_name(),
707 )
708 };
709 self.send_asset_event(InternalAssetEvent::Loaded {
710 id: handle.id(),
711 loaded_asset,
712 });
713 handle
714 }
715
716 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
721 pub fn add_async<A: Asset, E: std::error::Error + Send + Sync + 'static>(
722 &self,
723 future: impl Future<Output = Result<A, E>> + Send + 'static,
724 ) -> Handle<A> {
725 let handle = self
726 .data
727 .infos
728 .write()
729 .create_loading_handle_untyped(std::any::TypeId::of::<A>(), std::any::type_name::<A>());
730 let id = handle.id();
731
732 let event_sender = self.data.asset_event_sender.clone();
733
734 IoTaskPool::get()
735 .spawn(async move {
736 match future.await {
737 Ok(asset) => {
738 let loaded_asset = LoadedAsset::new_with_dependencies(asset, None).into();
739 event_sender
740 .send(InternalAssetEvent::Loaded { id, loaded_asset })
741 .unwrap();
742 }
743 Err(error) => {
744 let error = AddAsyncError {
745 error: Arc::new(error),
746 };
747 error!("{error}");
748 event_sender
749 .send(InternalAssetEvent::Failed {
750 id,
751 path: Default::default(),
752 error: AssetLoadError::AddAsyncError(error),
753 })
754 .unwrap();
755 }
756 }
757 })
758 .detach();
759
760 handle.typed_debug_checked()
761 }
762
763 #[must_use = "not using the returned strong handle may result in the unexpected release of the assets"]
772 pub fn load_folder<'a>(&self, path: impl Into<AssetPath<'a>>) -> Handle<LoadedFolder> {
773 let path = path.into().into_owned();
774 let (handle, should_load) = self
775 .data
776 .infos
777 .write()
778 .get_or_create_path_handle::<LoadedFolder>(
779 path.clone(),
780 HandleLoadingMode::Request,
781 None,
782 );
783 if !should_load {
784 return handle;
785 }
786 let id = handle.id().untyped();
787 self.load_folder_internal(id, path);
788
789 handle
790 }
791
792 pub(crate) fn load_folder_internal(&self, id: UntypedAssetId, path: AssetPath) {
793 async fn load_folder<'a>(
794 source: AssetSourceId<'static>,
795 path: &'a Path,
796 reader: &'a dyn ErasedAssetReader,
797 server: &'a AssetServer,
798 handles: &'a mut Vec<UntypedHandle>,
799 ) -> Result<(), AssetLoadError> {
800 let is_dir = reader.is_directory(path).await?;
801 if is_dir {
802 let mut path_stream = reader.read_directory(path.as_ref()).await?;
803 while let Some(child_path) = path_stream.next().await {
804 if reader.is_directory(&child_path).await? {
805 Box::pin(load_folder(
806 source.clone(),
807 &child_path,
808 reader,
809 server,
810 handles,
811 ))
812 .await?;
813 } else {
814 let path = child_path.to_str().expect("Path should be a valid string.");
815 let asset_path = AssetPath::parse(path).with_source(source.clone());
816 match server.load_untyped_async(asset_path).await {
817 Ok(handle) => handles.push(handle),
818 Err(
820 AssetLoadError::MissingAssetLoaderForTypeName(_)
821 | AssetLoadError::MissingAssetLoaderForExtension(_),
822 ) => {}
823 Err(err) => return Err(err),
824 }
825 }
826 }
827 }
828 Ok(())
829 }
830
831 let path = path.into_owned();
832 let server = self.clone();
833 IoTaskPool::get()
834 .spawn(async move {
835 let Ok(source) = server.get_source(path.source()) else {
836 error!(
837 "Failed to load {path}. AssetSource {:?} does not exist",
838 path.source()
839 );
840 return;
841 };
842
843 let asset_reader = match server.data.mode {
844 AssetServerMode::Unprocessed { .. } => source.reader(),
845 AssetServerMode::Processed { .. } => match source.processed_reader() {
846 Ok(reader) => reader,
847 Err(_) => {
848 error!(
849 "Failed to load {path}. AssetSource {:?} does not have a processed AssetReader",
850 path.source()
851 );
852 return;
853 }
854 },
855 };
856
857 let mut handles = Vec::new();
858 match load_folder(source.id(), path.path(), asset_reader, &server, &mut handles).await {
859 Ok(_) => server.send_asset_event(InternalAssetEvent::Loaded {
860 id,
861 loaded_asset: LoadedAsset::new_with_dependencies(
862 LoadedFolder { handles },
863 None,
864 )
865 .into(),
866 }),
867 Err(err) => {
868 error!("Failed to load folder. {err}");
869 server.send_asset_event(InternalAssetEvent::Failed { id, error: err, path });
870 },
871 }
872 })
873 .detach();
874 }
875
876 fn send_asset_event(&self, event: InternalAssetEvent) {
877 self.data.asset_event_sender.send(event).unwrap();
878 }
879
880 pub fn get_load_states(
882 &self,
883 id: impl Into<UntypedAssetId>,
884 ) -> Option<(LoadState, DependencyLoadState, RecursiveDependencyLoadState)> {
885 self.data
886 .infos
887 .read()
888 .get(id.into())
889 .map(|i| (i.load_state.clone(), i.dep_load_state, i.rec_dep_load_state))
890 }
891
892 pub fn get_load_state(&self, id: impl Into<UntypedAssetId>) -> Option<LoadState> {
897 self.data
898 .infos
899 .read()
900 .get(id.into())
901 .map(|i| i.load_state.clone())
902 }
903
904 pub fn get_recursive_dependency_load_state(
906 &self,
907 id: impl Into<UntypedAssetId>,
908 ) -> Option<RecursiveDependencyLoadState> {
909 self.data
910 .infos
911 .read()
912 .get(id.into())
913 .map(|i| i.rec_dep_load_state)
914 }
915
916 pub fn load_state(&self, id: impl Into<UntypedAssetId>) -> LoadState {
918 self.get_load_state(id).unwrap_or(LoadState::NotLoaded)
919 }
920
921 pub fn recursive_dependency_load_state(
923 &self,
924 id: impl Into<UntypedAssetId>,
925 ) -> RecursiveDependencyLoadState {
926 self.get_recursive_dependency_load_state(id)
927 .unwrap_or(RecursiveDependencyLoadState::NotLoaded)
928 }
929
930 pub fn is_loaded_with_dependencies(&self, id: impl Into<UntypedAssetId>) -> bool {
932 let id = id.into();
933 self.load_state(id) == LoadState::Loaded
934 && self.recursive_dependency_load_state(id) == RecursiveDependencyLoadState::Loaded
935 }
936
937 pub fn get_handle<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Option<Handle<A>> {
940 self.get_path_and_type_id_handle(&path.into(), TypeId::of::<A>())
941 .map(|h| h.typed_debug_checked())
942 }
943
944 pub fn get_id_handle<A: Asset>(&self, id: AssetId<A>) -> Option<Handle<A>> {
952 self.get_id_handle_untyped(id.untyped()).map(|h| h.typed())
953 }
954
955 pub fn get_id_handle_untyped(&self, id: UntypedAssetId) -> Option<UntypedHandle> {
958 self.data.infos.read().get_id_handle(id)
959 }
960
961 pub fn is_managed(&self, id: impl Into<UntypedAssetId>) -> bool {
964 self.data.infos.read().contains_key(id.into())
965 }
966
967 pub fn get_path_id<'a>(&self, path: impl Into<AssetPath<'a>>) -> Option<UntypedAssetId> {
974 let infos = self.data.infos.read();
975 let path = path.into();
976 let mut ids = infos.get_path_ids(&path);
977 ids.next()
978 }
979
980 pub fn get_path_ids<'a>(&self, path: impl Into<AssetPath<'a>>) -> Vec<UntypedAssetId> {
984 let infos = self.data.infos.read();
985 let path = path.into();
986 infos.get_path_ids(&path).collect()
987 }
988
989 pub fn get_handle_untyped<'a>(&self, path: impl Into<AssetPath<'a>>) -> Option<UntypedHandle> {
996 let infos = self.data.infos.read();
997 let path = path.into();
998 let mut handles = infos.get_path_handles(&path);
999 handles.next()
1000 }
1001
1002 pub fn get_handles_untyped<'a>(&self, path: impl Into<AssetPath<'a>>) -> Vec<UntypedHandle> {
1006 let infos = self.data.infos.read();
1007 let path = path.into();
1008 infos.get_path_handles(&path).collect()
1009 }
1010
1011 pub fn get_path_and_type_id_handle(
1014 &self,
1015 path: &AssetPath,
1016 type_id: TypeId,
1017 ) -> Option<UntypedHandle> {
1018 let infos = self.data.infos.read();
1019 let path = path.into();
1020 infos.get_path_and_type_id_handle(&path, type_id)
1021 }
1022
1023 pub fn get_path(&self, id: impl Into<UntypedAssetId>) -> Option<AssetPath> {
1025 let infos = self.data.infos.read();
1026 let info = infos.get(id.into())?;
1027 Some(info.path.as_ref()?.clone())
1028 }
1029
1030 pub fn mode(&self) -> AssetServerMode {
1032 self.data.mode
1033 }
1034
1035 pub fn preregister_loader<L: AssetLoader>(&self, extensions: &[&str]) {
1040 self.data.loaders.write().reserve::<L>(extensions);
1041 }
1042
1043 pub(crate) fn get_or_create_path_handle<'a, A: Asset>(
1045 &self,
1046 path: impl Into<AssetPath<'a>>,
1047 meta_transform: Option<MetaTransform>,
1048 ) -> Handle<A> {
1049 let mut infos = self.data.infos.write();
1050 infos
1051 .get_or_create_path_handle::<A>(
1052 path.into().into_owned(),
1053 HandleLoadingMode::NotLoading,
1054 meta_transform,
1055 )
1056 .0
1057 }
1058
1059 pub(crate) async fn get_meta_loader_and_reader<'a>(
1060 &'a self,
1061 asset_path: &'a AssetPath<'_>,
1062 asset_type_id: Option<TypeId>,
1063 ) -> Result<
1064 (
1065 Box<dyn AssetMetaDyn>,
1066 Arc<dyn ErasedAssetLoader>,
1067 Box<Reader<'a>>,
1068 ),
1069 AssetLoadError,
1070 > {
1071 let source = self.get_source(asset_path.source())?;
1072 let asset_reader = match self.data.mode {
1077 AssetServerMode::Unprocessed { .. } => source.reader(),
1078 AssetServerMode::Processed { .. } => source.processed_reader()?,
1079 };
1080 let reader = asset_reader.read(asset_path.path()).await?;
1081 let read_meta = match &self.data.meta_check {
1082 AssetMetaCheck::Always => true,
1083 AssetMetaCheck::Paths(paths) => paths.contains(asset_path),
1084 AssetMetaCheck::Never => false,
1085 };
1086
1087 if read_meta {
1088 match asset_reader.read_meta_bytes(asset_path.path()).await {
1089 Ok(meta_bytes) => {
1090 let minimal: AssetMetaMinimal =
1092 ron::de::from_bytes(&meta_bytes).map_err(|e| {
1093 AssetLoadError::DeserializeMeta {
1094 path: asset_path.clone_owned(),
1095 error: DeserializeMetaError::DeserializeMinimal(e).into(),
1096 }
1097 })?;
1098 let loader_name = match minimal.asset {
1099 AssetActionMinimal::Load { loader } => loader,
1100 AssetActionMinimal::Process { .. } => {
1101 return Err(AssetLoadError::CannotLoadProcessedAsset {
1102 path: asset_path.clone_owned(),
1103 })
1104 }
1105 AssetActionMinimal::Ignore => {
1106 return Err(AssetLoadError::CannotLoadIgnoredAsset {
1107 path: asset_path.clone_owned(),
1108 })
1109 }
1110 };
1111 let loader = self.get_asset_loader_with_type_name(&loader_name).await?;
1112 let meta = loader.deserialize_meta(&meta_bytes).map_err(|e| {
1113 AssetLoadError::DeserializeMeta {
1114 path: asset_path.clone_owned(),
1115 error: e.into(),
1116 }
1117 })?;
1118
1119 Ok((meta, loader, reader))
1120 }
1121 Err(AssetReaderError::NotFound(_)) => {
1122 let loader = {
1124 self.data
1125 .loaders
1126 .read()
1127 .find(None, asset_type_id, None, Some(asset_path))
1128 };
1129
1130 let error = || AssetLoadError::MissingAssetLoader {
1131 loader_name: None,
1132 asset_type_id,
1133 extension: None,
1134 asset_path: Some(asset_path.to_string()),
1135 };
1136
1137 let loader = loader.ok_or_else(error)?.get().await.map_err(|_| error())?;
1138
1139 let meta = loader.default_meta();
1140 Ok((meta, loader, reader))
1141 }
1142 Err(err) => Err(err.into()),
1143 }
1144 } else {
1145 let loader = {
1146 self.data
1147 .loaders
1148 .read()
1149 .find(None, asset_type_id, None, Some(asset_path))
1150 };
1151
1152 let error = || AssetLoadError::MissingAssetLoader {
1153 loader_name: None,
1154 asset_type_id,
1155 extension: None,
1156 asset_path: Some(asset_path.to_string()),
1157 };
1158
1159 let loader = loader.ok_or_else(error)?.get().await.map_err(|_| error())?;
1160
1161 let meta = loader.default_meta();
1162 Ok((meta, loader, reader))
1163 }
1164 }
1165
1166 pub(crate) async fn load_with_meta_loader_and_reader(
1167 &self,
1168 asset_path: &AssetPath<'_>,
1169 meta: Box<dyn AssetMetaDyn>,
1170 loader: &dyn ErasedAssetLoader,
1171 reader: &mut Reader<'_>,
1172 load_dependencies: bool,
1173 populate_hashes: bool,
1174 ) -> Result<ErasedLoadedAsset, AssetLoadError> {
1175 let asset_path = asset_path.clone_owned();
1177 let load_context =
1178 LoadContext::new(self, asset_path.clone(), load_dependencies, populate_hashes);
1179 loader.load(reader, meta, load_context).await.map_err(|e| {
1180 AssetLoadError::AssetLoaderError(AssetLoaderError {
1181 path: asset_path.clone_owned(),
1182 loader_name: loader.type_name(),
1183 error: e.into(),
1184 })
1185 })
1186 }
1187}
1188
1189pub fn handle_internal_asset_events(world: &mut World) {
1191 world.resource_scope(|world, server: Mut<AssetServer>| {
1192 let mut infos = server.data.infos.write();
1193 let mut untyped_failures = vec![];
1194 for event in server.data.asset_event_receiver.try_iter() {
1195 match event {
1196 InternalAssetEvent::Loaded { id, loaded_asset } => {
1197 infos.process_asset_load(
1198 id,
1199 loaded_asset,
1200 world,
1201 &server.data.asset_event_sender,
1202 );
1203 }
1204 InternalAssetEvent::LoadedWithDependencies { id } => {
1205 let sender = infos
1206 .dependency_loaded_event_sender
1207 .get(&id.type_id())
1208 .expect("Asset event sender should exist");
1209 sender(world, id);
1210 }
1211 InternalAssetEvent::Failed { id, path, error } => {
1212 infos.process_asset_fail(id, error.clone());
1213
1214 untyped_failures.push(UntypedAssetLoadFailedEvent {
1216 id,
1217 path: path.clone(),
1218 error: error.clone(),
1219 });
1220
1221 let sender = infos
1223 .dependency_failed_event_sender
1224 .get(&id.type_id())
1225 .expect("Asset failed event sender should exist");
1226 sender(world, id, path, error);
1227 }
1228 }
1229 }
1230
1231 if !untyped_failures.is_empty() {
1232 world.send_event_batch(untyped_failures);
1233 }
1234
1235 fn queue_ancestors(
1236 asset_path: &AssetPath,
1237 infos: &AssetInfos,
1238 paths_to_reload: &mut HashSet<AssetPath<'static>>,
1239 ) {
1240 if let Some(dependants) = infos.loader_dependants.get(asset_path) {
1241 for dependant in dependants {
1242 paths_to_reload.insert(dependant.to_owned());
1243 queue_ancestors(dependant, infos, paths_to_reload);
1244 }
1245 }
1246 }
1247
1248 let reload_parent_folders = |path: PathBuf, source: &AssetSourceId<'static>| {
1249 let mut current_folder = path;
1250 while let Some(parent) = current_folder.parent() {
1251 current_folder = parent.to_path_buf();
1252 let parent_asset_path =
1253 AssetPath::from(current_folder.clone()).with_source(source.clone());
1254 for folder_handle in infos.get_path_handles(&parent_asset_path) {
1255 info!("Reloading folder {parent_asset_path} because the content has changed");
1256 server.load_folder_internal(folder_handle.id(), parent_asset_path.clone());
1257 }
1258 }
1259 };
1260
1261 let mut paths_to_reload = HashSet::new();
1262 let mut handle_event = |source: AssetSourceId<'static>, event: AssetSourceEvent| {
1263 match event {
1264 AssetSourceEvent::ModifiedAsset(path) | AssetSourceEvent::ModifiedMeta(path) => {
1267 let path = AssetPath::from(path).with_source(source);
1268 queue_ancestors(&path, &infos, &mut paths_to_reload);
1269 paths_to_reload.insert(path);
1270 }
1271 AssetSourceEvent::RenamedFolder { old, new } => {
1272 reload_parent_folders(old, &source);
1273 reload_parent_folders(new, &source);
1274 }
1275 AssetSourceEvent::AddedAsset(path)
1276 | AssetSourceEvent::RemovedAsset(path)
1277 | AssetSourceEvent::RemovedFolder(path)
1278 | AssetSourceEvent::AddedFolder(path) => {
1279 reload_parent_folders(path, &source);
1280 }
1281 _ => {}
1282 }
1283 };
1284
1285 for source in server.data.sources.iter() {
1286 match server.data.mode {
1287 AssetServerMode::Unprocessed { .. } => {
1288 if let Some(receiver) = source.event_receiver() {
1289 for event in receiver.try_iter() {
1290 handle_event(source.id(), event);
1291 }
1292 }
1293 }
1294 AssetServerMode::Processed { .. } => {
1295 if let Some(receiver) = source.processed_event_receiver() {
1296 for event in receiver.try_iter() {
1297 handle_event(source.id(), event);
1298 }
1299 }
1300 }
1301 }
1302 }
1303
1304 for path in paths_to_reload {
1305 info!("Reloading {path} because it has changed");
1306 server.reload(path);
1307 }
1308 });
1309}
1310
1311#[allow(clippy::large_enum_variant)]
1313pub(crate) enum InternalAssetEvent {
1314 Loaded {
1315 id: UntypedAssetId,
1316 loaded_asset: ErasedLoadedAsset,
1317 },
1318 LoadedWithDependencies {
1319 id: UntypedAssetId,
1320 },
1321 Failed {
1322 id: UntypedAssetId,
1323 path: AssetPath<'static>,
1324 error: AssetLoadError,
1325 },
1326}
1327
1328#[derive(Component, Clone, Debug, PartialEq, Eq)]
1330pub enum LoadState {
1331 NotLoaded,
1333 Loading,
1335 Loaded,
1337 Failed(Box<AssetLoadError>),
1339}
1340
1341#[derive(Component, Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
1343pub enum DependencyLoadState {
1344 NotLoaded,
1346 Loading,
1348 Loaded,
1350 Failed,
1352}
1353
1354#[derive(Component, Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
1356pub enum RecursiveDependencyLoadState {
1357 NotLoaded,
1359 Loading,
1361 Loaded,
1363 Failed,
1365}
1366
1367#[derive(Error, Debug, Clone, PartialEq, Eq)]
1369pub enum AssetLoadError {
1370 #[error("Requested handle of type {requested:?} for asset '{path}' does not match actual asset type '{actual_asset_name}', which used loader '{loader_name}'")]
1371 RequestedHandleTypeMismatch {
1372 path: AssetPath<'static>,
1373 requested: TypeId,
1374 actual_asset_name: &'static str,
1375 loader_name: &'static str,
1376 },
1377 #[error("Could not find an asset loader matching: Loader Name: {loader_name:?}; Asset Type: {loader_name:?}; Extension: {extension:?}; Path: {asset_path:?};")]
1378 MissingAssetLoader {
1379 loader_name: Option<String>,
1380 asset_type_id: Option<TypeId>,
1381 extension: Option<String>,
1382 asset_path: Option<String>,
1383 },
1384 #[error(transparent)]
1385 MissingAssetLoaderForExtension(#[from] MissingAssetLoaderForExtensionError),
1386 #[error(transparent)]
1387 MissingAssetLoaderForTypeName(#[from] MissingAssetLoaderForTypeNameError),
1388 #[error(transparent)]
1389 MissingAssetLoaderForTypeIdError(#[from] MissingAssetLoaderForTypeIdError),
1390 #[error(transparent)]
1391 AssetReaderError(#[from] AssetReaderError),
1392 #[error(transparent)]
1393 MissingAssetSourceError(#[from] MissingAssetSourceError),
1394 #[error(transparent)]
1395 MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError),
1396 #[error("Encountered an error while reading asset metadata bytes")]
1397 AssetMetaReadError,
1398 #[error("Failed to deserialize meta for asset {path}: {error}")]
1399 DeserializeMeta {
1400 path: AssetPath<'static>,
1401 error: Box<DeserializeMetaError>,
1402 },
1403 #[error("Asset '{path}' is configured to be processed. It cannot be loaded directly.")]
1404 CannotLoadProcessedAsset { path: AssetPath<'static> },
1405 #[error("Asset '{path}' is configured to be ignored. It cannot be loaded.")]
1406 CannotLoadIgnoredAsset { path: AssetPath<'static> },
1407 #[error(transparent)]
1408 AssetLoaderError(#[from] AssetLoaderError),
1409 #[error(transparent)]
1410 AddAsyncError(#[from] AddAsyncError),
1411 #[error("The file at '{}' does not contain the labeled asset '{}'; it contains the following {} assets: {}",
1412 base_path,
1413 label,
1414 all_labels.len(),
1415 all_labels.iter().map(|l| format!("'{}'", l)).collect::<Vec<_>>().join(", "))]
1416 MissingLabel {
1417 base_path: AssetPath<'static>,
1418 label: String,
1419 all_labels: Vec<String>,
1420 },
1421}
1422
1423#[derive(Error, Debug, Clone)]
1424#[error("Failed to load asset '{path}' with asset loader '{loader_name}': {error}")]
1425pub struct AssetLoaderError {
1426 path: AssetPath<'static>,
1427 loader_name: &'static str,
1428 error: Arc<dyn std::error::Error + Send + Sync + 'static>,
1429}
1430
1431impl PartialEq for AssetLoaderError {
1432 #[inline]
1434 fn eq(&self, other: &Self) -> bool {
1435 self.path == other.path
1436 && self.loader_name == other.loader_name
1437 && self.error.type_id() == other.error.type_id()
1438 }
1439}
1440
1441impl Eq for AssetLoaderError {}
1442
1443impl AssetLoaderError {
1444 pub fn path(&self) -> &AssetPath<'static> {
1445 &self.path
1446 }
1447}
1448
1449#[derive(Error, Debug, Clone)]
1450#[error("An error occurred while resolving an asset added by `add_async`: {error}")]
1451pub struct AddAsyncError {
1452 error: Arc<dyn std::error::Error + Send + Sync + 'static>,
1453}
1454
1455impl PartialEq for AddAsyncError {
1456 #[inline]
1458 fn eq(&self, other: &Self) -> bool {
1459 self.error.type_id() == other.error.type_id()
1460 }
1461}
1462
1463impl Eq for AddAsyncError {}
1464
1465#[derive(Error, Debug, Clone, PartialEq, Eq)]
1467#[error("no `AssetLoader` found{}", format_missing_asset_ext(.extensions))]
1468pub struct MissingAssetLoaderForExtensionError {
1469 extensions: Vec<String>,
1470}
1471
1472#[derive(Error, Debug, Clone, PartialEq, Eq)]
1474#[error("no `AssetLoader` found with the name '{type_name}'")]
1475pub struct MissingAssetLoaderForTypeNameError {
1476 type_name: String,
1477}
1478
1479#[derive(Error, Debug, Clone, PartialEq, Eq)]
1481#[error("no `AssetLoader` found with the ID '{type_id:?}'")]
1482pub struct MissingAssetLoaderForTypeIdError {
1483 pub type_id: TypeId,
1484}
1485
1486fn format_missing_asset_ext(exts: &[String]) -> String {
1487 if !exts.is_empty() {
1488 format!(
1489 " for the following extension{}: {}",
1490 if exts.len() > 1 { "s" } else { "" },
1491 exts.join(", ")
1492 )
1493 } else {
1494 " for file with no extension".to_string()
1495 }
1496}
1497
1498impl std::fmt::Debug for AssetServer {
1499 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1500 f.debug_struct("AssetServer")
1501 .field("info", &self.data.infos.read())
1502 .finish()
1503 }
1504}
1505
1506const UNTYPED_SOURCE_SUFFIX: &str = "--untyped";