1mod info;
2mod loaders;
3
4use crate::{
5 folder::LoadedFolder,
6 io::{
7 AssetReaderError, AssetSource, AssetSourceEvent, AssetSourceId, AssetSources,
8 AssetWriterError, ErasedAssetReader, MissingAssetSourceError, MissingAssetWriterError,
9 MissingProcessedAssetReaderError, Reader,
10 },
11 loader::{AssetLoader, ErasedAssetLoader, LoadContext, LoadedAsset},
12 meta::{
13 loader_settings_meta_transform, AssetActionMinimal, AssetMetaDyn, AssetMetaMinimal,
14 MetaTransform, Settings,
15 },
16 path::AssetPath,
17 Asset, AssetEvent, AssetHandleProvider, AssetId, AssetLoadFailedEvent, AssetMetaCheck, Assets,
18 DeserializeMetaError, ErasedLoadedAsset, Handle, LoadedUntypedAsset, UnapprovedPathMode,
19 UntypedAssetId, UntypedAssetLoadFailedEvent, UntypedHandle,
20};
21use alloc::{borrow::ToOwned, boxed::Box, vec, vec::Vec};
22use alloc::{
23 format,
24 string::{String, ToString},
25 sync::Arc,
26};
27use atomicow::CowArc;
28use bevy_ecs::prelude::*;
29use bevy_platform::collections::HashSet;
30use bevy_tasks::IoTaskPool;
31use core::{any::TypeId, future::Future, panic::AssertUnwindSafe, task::Poll};
32use crossbeam_channel::{Receiver, Sender};
33use either::Either;
34use futures_lite::{FutureExt, StreamExt};
35use info::*;
36use loaders::*;
37use parking_lot::{RwLock, RwLockWriteGuard};
38use std::path::{Path, PathBuf};
39use thiserror::Error;
40use tracing::{error, info};
41
42#[derive(Resource, Clone)]
58pub struct AssetServer {
59 pub(crate) data: Arc<AssetServerData>,
60}
61
62pub(crate) struct AssetServerData {
64 pub(crate) infos: RwLock<AssetInfos>,
65 pub(crate) loaders: Arc<RwLock<AssetLoaders>>,
66 asset_event_sender: Sender<InternalAssetEvent>,
67 asset_event_receiver: Receiver<InternalAssetEvent>,
68 sources: AssetSources,
69 mode: AssetServerMode,
70 meta_check: AssetMetaCheck,
71 unapproved_path_mode: UnapprovedPathMode,
72}
73
74#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76pub enum AssetServerMode {
77 Unprocessed,
79 Processed,
81}
82
83impl AssetServer {
84 pub fn new(
87 sources: AssetSources,
88 mode: AssetServerMode,
89 watching_for_changes: bool,
90 unapproved_path_mode: UnapprovedPathMode,
91 ) -> Self {
92 Self::new_with_loaders(
93 sources,
94 Default::default(),
95 mode,
96 AssetMetaCheck::Always,
97 watching_for_changes,
98 unapproved_path_mode,
99 )
100 }
101
102 pub fn new_with_meta_check(
105 sources: AssetSources,
106 mode: AssetServerMode,
107 meta_check: AssetMetaCheck,
108 watching_for_changes: bool,
109 unapproved_path_mode: UnapprovedPathMode,
110 ) -> Self {
111 Self::new_with_loaders(
112 sources,
113 Default::default(),
114 mode,
115 meta_check,
116 watching_for_changes,
117 unapproved_path_mode,
118 )
119 }
120
121 pub(crate) fn new_with_loaders(
122 sources: AssetSources,
123 loaders: Arc<RwLock<AssetLoaders>>,
124 mode: AssetServerMode,
125 meta_check: AssetMetaCheck,
126 watching_for_changes: bool,
127 unapproved_path_mode: UnapprovedPathMode,
128 ) -> Self {
129 let (asset_event_sender, asset_event_receiver) = crossbeam_channel::unbounded();
130 let mut infos = AssetInfos::default();
131 infos.watching_for_changes = watching_for_changes;
132 Self {
133 data: Arc::new(AssetServerData {
134 sources,
135 mode,
136 meta_check,
137 asset_event_sender,
138 asset_event_receiver,
139 loaders,
140 infos: RwLock::new(infos),
141 unapproved_path_mode,
142 }),
143 }
144 }
145
146 pub fn get_source<'a>(
148 &self,
149 source: impl Into<AssetSourceId<'a>>,
150 ) -> Result<&AssetSource, MissingAssetSourceError> {
151 self.data.sources.get(source.into())
152 }
153
154 pub fn watching_for_changes(&self) -> bool {
156 self.data.infos.read().watching_for_changes
157 }
158
159 pub fn register_loader<L: AssetLoader>(&self, loader: L) {
161 self.data.loaders.write().push(loader);
162 }
163
164 pub fn register_asset<A: Asset>(&self, assets: &Assets<A>) {
166 self.register_handle_provider(assets.get_handle_provider());
167 fn sender<A: Asset>(world: &mut World, id: UntypedAssetId) {
168 world
169 .resource_mut::<Events<AssetEvent<A>>>()
170 .send(AssetEvent::LoadedWithDependencies { id: id.typed() });
171 }
172 fn failed_sender<A: Asset>(
173 world: &mut World,
174 id: UntypedAssetId,
175 path: AssetPath<'static>,
176 error: AssetLoadError,
177 ) {
178 world
179 .resource_mut::<Events<AssetLoadFailedEvent<A>>>()
180 .send(AssetLoadFailedEvent {
181 id: id.typed(),
182 path,
183 error,
184 });
185 }
186
187 let mut infos = self.data.infos.write();
188
189 infos
190 .dependency_loaded_event_sender
191 .insert(TypeId::of::<A>(), sender::<A>);
192
193 infos
194 .dependency_failed_event_sender
195 .insert(TypeId::of::<A>(), failed_sender::<A>);
196 }
197
198 pub(crate) fn register_handle_provider(&self, handle_provider: AssetHandleProvider) {
199 let mut infos = self.data.infos.write();
200 infos
201 .handle_providers
202 .insert(handle_provider.type_id, handle_provider);
203 }
204
205 pub async fn get_asset_loader_with_extension(
207 &self,
208 extension: &str,
209 ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForExtensionError> {
210 let error = || MissingAssetLoaderForExtensionError {
211 extensions: vec![extension.to_string()],
212 };
213
214 let loader = { self.data.loaders.read().get_by_extension(extension) };
215
216 loader.ok_or_else(error)?.get().await.map_err(|_| error())
217 }
218
219 pub async fn get_asset_loader_with_type_name(
221 &self,
222 type_name: &str,
223 ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForTypeNameError> {
224 let error = || MissingAssetLoaderForTypeNameError {
225 type_name: type_name.to_string(),
226 };
227
228 let loader = { self.data.loaders.read().get_by_name(type_name) };
229
230 loader.ok_or_else(error)?.get().await.map_err(|_| error())
231 }
232
233 pub async fn get_path_asset_loader<'a>(
235 &self,
236 path: impl Into<AssetPath<'a>>,
237 ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForExtensionError> {
238 let path = path.into();
239
240 let error = || {
241 let Some(full_extension) = path.get_full_extension() else {
242 return MissingAssetLoaderForExtensionError {
243 extensions: Vec::new(),
244 };
245 };
246
247 let mut extensions = vec![full_extension.clone()];
248 extensions.extend(
249 AssetPath::iter_secondary_extensions(&full_extension).map(ToString::to_string),
250 );
251
252 MissingAssetLoaderForExtensionError { extensions }
253 };
254
255 let loader = { self.data.loaders.read().get_by_path(&path) };
256
257 loader.ok_or_else(error)?.get().await.map_err(|_| error())
258 }
259
260 pub async fn get_asset_loader_with_asset_type_id(
262 &self,
263 type_id: TypeId,
264 ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForTypeIdError> {
265 let error = || MissingAssetLoaderForTypeIdError { type_id };
266
267 let loader = { self.data.loaders.read().get_by_type(type_id) };
268
269 loader.ok_or_else(error)?.get().await.map_err(|_| error())
270 }
271
272 pub async fn get_asset_loader_with_asset_type<A: Asset>(
274 &self,
275 ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForTypeIdError> {
276 self.get_asset_loader_with_asset_type_id(TypeId::of::<A>())
277 .await
278 }
279
280 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
324 pub fn load<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Handle<A> {
325 self.load_with_meta_transform(path, None, (), false)
326 }
327
328 pub fn load_override<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Handle<A> {
334 self.load_with_meta_transform(path, None, (), true)
335 }
336
337 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
353 pub fn load_acquire<'a, A: Asset, G: Send + Sync + 'static>(
354 &self,
355 path: impl Into<AssetPath<'a>>,
356 guard: G,
357 ) -> Handle<A> {
358 self.load_with_meta_transform(path, None, guard, false)
359 }
360
361 pub fn load_acquire_override<'a, A: Asset, G: Send + Sync + 'static>(
367 &self,
368 path: impl Into<AssetPath<'a>>,
369 guard: G,
370 ) -> Handle<A> {
371 self.load_with_meta_transform(path, None, guard, true)
372 }
373
374 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
378 pub fn load_with_settings<'a, A: Asset, S: Settings>(
379 &self,
380 path: impl Into<AssetPath<'a>>,
381 settings: impl Fn(&mut S) + Send + Sync + 'static,
382 ) -> Handle<A> {
383 self.load_with_meta_transform(
384 path,
385 Some(loader_settings_meta_transform(settings)),
386 (),
387 false,
388 )
389 }
390
391 pub fn load_with_settings_override<'a, A: Asset, S: Settings>(
397 &self,
398 path: impl Into<AssetPath<'a>>,
399 settings: impl Fn(&mut S) + Send + Sync + 'static,
400 ) -> Handle<A> {
401 self.load_with_meta_transform(
402 path,
403 Some(loader_settings_meta_transform(settings)),
404 (),
405 true,
406 )
407 }
408
409 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
419 pub fn load_acquire_with_settings<'a, A: Asset, S: Settings, G: Send + Sync + 'static>(
420 &self,
421 path: impl Into<AssetPath<'a>>,
422 settings: impl Fn(&mut S) + Send + Sync + 'static,
423 guard: G,
424 ) -> Handle<A> {
425 self.load_with_meta_transform(
426 path,
427 Some(loader_settings_meta_transform(settings)),
428 guard,
429 false,
430 )
431 }
432
433 pub fn load_acquire_with_settings_override<
439 'a,
440 A: Asset,
441 S: Settings,
442 G: Send + Sync + 'static,
443 >(
444 &self,
445 path: impl Into<AssetPath<'a>>,
446 settings: impl Fn(&mut S) + Send + Sync + 'static,
447 guard: G,
448 ) -> Handle<A> {
449 self.load_with_meta_transform(
450 path,
451 Some(loader_settings_meta_transform(settings)),
452 guard,
453 true,
454 )
455 }
456
457 pub(crate) fn load_with_meta_transform<'a, A: Asset, G: Send + Sync + 'static>(
458 &self,
459 path: impl Into<AssetPath<'a>>,
460 meta_transform: Option<MetaTransform>,
461 guard: G,
462 override_unapproved: bool,
463 ) -> Handle<A> {
464 let path = path.into().into_owned();
465
466 if path.is_unapproved() {
467 match (&self.data.unapproved_path_mode, override_unapproved) {
468 (UnapprovedPathMode::Allow, _) | (UnapprovedPathMode::Deny, true) => {}
469 (UnapprovedPathMode::Deny, false) | (UnapprovedPathMode::Forbid, _) => {
470 error!("Asset path {path} is unapproved. See UnapprovedPathMode for details.");
471 return Handle::default();
472 }
473 }
474 }
475
476 let mut infos = self.data.infos.write();
477 let (handle, should_load) = infos.get_or_create_path_handle::<A>(
478 path.clone(),
479 HandleLoadingMode::Request,
480 meta_transform,
481 );
482
483 if should_load {
484 self.spawn_load_task(handle.clone().untyped(), path, infos, guard);
485 }
486
487 handle
488 }
489
490 pub(crate) fn load_erased_with_meta_transform<'a, G: Send + Sync + 'static>(
491 &self,
492 path: impl Into<AssetPath<'a>>,
493 type_id: TypeId,
494 meta_transform: Option<MetaTransform>,
495 guard: G,
496 ) -> UntypedHandle {
497 let path = path.into().into_owned();
498 let mut infos = self.data.infos.write();
499 let (handle, should_load) = infos.get_or_create_path_handle_erased(
500 path.clone(),
501 type_id,
502 None,
503 HandleLoadingMode::Request,
504 meta_transform,
505 );
506
507 if should_load {
508 self.spawn_load_task(handle.clone(), path, infos, guard);
509 }
510
511 handle
512 }
513
514 pub(crate) fn spawn_load_task<G: Send + Sync + 'static>(
515 &self,
516 handle: UntypedHandle,
517 path: AssetPath<'static>,
518 infos: RwLockWriteGuard<AssetInfos>,
519 guard: G,
520 ) {
521 #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
523 drop(infos);
524
525 let owned_handle = handle.clone();
526 let server = self.clone();
527 let task = IoTaskPool::get().spawn(async move {
528 if let Err(err) = server
529 .load_internal(Some(owned_handle), path, false, None)
530 .await
531 {
532 error!("{}", err);
533 }
534 drop(guard);
535 });
536
537 #[cfg(not(any(target_arch = "wasm32", not(feature = "multi_threaded"))))]
538 {
539 let mut infos = infos;
540 infos.pending_tasks.insert(handle.id(), task);
541 }
542
543 #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
544 task.detach();
545 }
546
547 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
551 pub async fn load_untyped_async<'a>(
552 &self,
553 path: impl Into<AssetPath<'a>>,
554 ) -> Result<UntypedHandle, AssetLoadError> {
555 let path: AssetPath = path.into();
556 self.load_internal(None, path, false, None).await
557 }
558
559 pub(crate) fn load_unknown_type_with_meta_transform<'a>(
560 &self,
561 path: impl Into<AssetPath<'a>>,
562 meta_transform: Option<MetaTransform>,
563 ) -> Handle<LoadedUntypedAsset> {
564 let path = path.into().into_owned();
565 let untyped_source = AssetSourceId::Name(match path.source() {
566 AssetSourceId::Default => CowArc::Static(UNTYPED_SOURCE_SUFFIX),
567 AssetSourceId::Name(source) => {
568 CowArc::Owned(format!("{source}--{UNTYPED_SOURCE_SUFFIX}").into())
569 }
570 });
571 let mut infos = self.data.infos.write();
572 let (handle, should_load) = infos.get_or_create_path_handle::<LoadedUntypedAsset>(
573 path.clone().with_source(untyped_source),
574 HandleLoadingMode::Request,
575 meta_transform,
576 );
577
578 #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
580 drop(infos);
581
582 if !should_load {
583 return handle;
584 }
585 let id = handle.id().untyped();
586
587 let server = self.clone();
588 let task = IoTaskPool::get().spawn(async move {
589 let path_clone = path.clone();
590 match server.load_untyped_async(path).await {
591 Ok(handle) => server.send_asset_event(InternalAssetEvent::Loaded {
592 id,
593 loaded_asset: LoadedAsset::new_with_dependencies(LoadedUntypedAsset { handle })
594 .into(),
595 }),
596 Err(err) => {
597 error!("{err}");
598 server.send_asset_event(InternalAssetEvent::Failed {
599 id,
600 path: path_clone,
601 error: err,
602 });
603 }
604 }
605 });
606
607 #[cfg(not(any(target_arch = "wasm32", not(feature = "multi_threaded"))))]
608 infos.pending_tasks.insert(handle.id().untyped(), task);
609
610 #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
611 task.detach();
612
613 handle
614 }
615
616 #[must_use = "not using the returned strong handle may result in the unexpected release of the assets"]
640 pub fn load_untyped<'a>(&self, path: impl Into<AssetPath<'a>>) -> Handle<LoadedUntypedAsset> {
641 self.load_unknown_type_with_meta_transform(path, None)
642 }
643
644 async fn load_internal<'a>(
649 &self,
650 mut input_handle: Option<UntypedHandle>,
651 path: AssetPath<'a>,
652 force: bool,
653 meta_transform: Option<MetaTransform>,
654 ) -> Result<UntypedHandle, AssetLoadError> {
655 let asset_type_id = input_handle.as_ref().map(UntypedHandle::type_id);
656
657 let path = path.into_owned();
658 let path_clone = path.clone();
659 let (mut meta, loader, mut reader) = self
660 .get_meta_loader_and_reader(&path_clone, asset_type_id)
661 .await
662 .inspect_err(|e| {
663 if let Some(handle) = &input_handle {
666 self.send_asset_event(InternalAssetEvent::Failed {
667 id: handle.id(),
668 path: path.clone_owned(),
669 error: e.clone(),
670 });
671 }
672 })?;
673
674 if let Some(meta_transform) = input_handle.as_ref().and_then(|h| h.meta_transform()) {
675 (*meta_transform)(&mut *meta);
676 }
677 input_handle = input_handle.map(|h| h.clone_weak());
680
681 let handle_result = match input_handle {
695 Some(handle) => {
696 Some((handle, true))
698 }
699 None => {
700 let mut infos = self.data.infos.write();
701 let result = infos.get_or_create_path_handle_internal(
702 path.clone(),
703 path.label().is_none().then(|| loader.asset_type_id()),
704 HandleLoadingMode::Request,
705 meta_transform,
706 );
707 unwrap_with_context(result, Either::Left(loader.asset_type_name()))
708 }
709 };
710
711 let handle = if let Some((handle, should_load)) = handle_result {
712 if path.label().is_none() && handle.type_id() != loader.asset_type_id() {
713 error!(
714 "Expected {:?}, got {:?}",
715 handle.type_id(),
716 loader.asset_type_id()
717 );
718 return Err(AssetLoadError::RequestedHandleTypeMismatch {
719 path: path.into_owned(),
720 requested: handle.type_id(),
721 actual_asset_name: loader.asset_type_name(),
722 loader_name: loader.type_name(),
723 });
724 }
725 if !should_load && !force {
726 return Ok(handle);
727 }
728 Some(handle)
729 } else {
730 None
731 };
732 let (base_handle, base_path) = if path.label().is_some() {
735 let mut infos = self.data.infos.write();
736 let base_path = path.without_label().into_owned();
737 let (base_handle, _) = infos.get_or_create_path_handle_erased(
738 base_path.clone(),
739 loader.asset_type_id(),
740 Some(loader.asset_type_name()),
741 HandleLoadingMode::Force,
742 None,
743 );
744 (base_handle, base_path)
745 } else {
746 (handle.clone().unwrap(), path.clone())
747 };
748
749 match self
750 .load_with_meta_loader_and_reader(
751 &base_path,
752 meta.as_ref(),
753 &*loader,
754 &mut *reader,
755 true,
756 false,
757 )
758 .await
759 {
760 Ok(loaded_asset) => {
761 let final_handle = if let Some(label) = path.label_cow() {
762 match loaded_asset.labeled_assets.get(&label) {
763 Some(labeled_asset) => labeled_asset.handle.clone(),
764 None => {
765 let mut all_labels: Vec<String> = loaded_asset
766 .labeled_assets
767 .keys()
768 .map(|s| (**s).to_owned())
769 .collect();
770 all_labels.sort_unstable();
771 return Err(AssetLoadError::MissingLabel {
772 base_path,
773 label: label.to_string(),
774 all_labels,
775 });
776 }
777 }
778 } else {
779 handle.unwrap()
781 };
782
783 self.send_loaded_asset(base_handle.id(), loaded_asset);
784 Ok(final_handle)
785 }
786 Err(err) => {
787 self.send_asset_event(InternalAssetEvent::Failed {
788 id: base_handle.id(),
789 error: err.clone(),
790 path: path.into_owned(),
791 });
792 Err(err)
793 }
794 }
795 }
796
797 fn send_loaded_asset(&self, id: UntypedAssetId, mut loaded_asset: ErasedLoadedAsset) {
800 for (_, labeled_asset) in loaded_asset.labeled_assets.drain() {
801 self.send_loaded_asset(labeled_asset.handle.id(), labeled_asset.asset);
802 }
803
804 self.send_asset_event(InternalAssetEvent::Loaded { id, loaded_asset });
805 }
806
807 pub fn reload<'a>(&self, path: impl Into<AssetPath<'a>>) {
809 let server = self.clone();
810 let path = path.into().into_owned();
811 IoTaskPool::get()
812 .spawn(async move {
813 let mut reloaded = false;
814
815 let requests = server
816 .data
817 .infos
818 .read()
819 .get_path_handles(&path)
820 .map(|handle| server.load_internal(Some(handle), path.clone(), true, None))
821 .collect::<Vec<_>>();
822
823 for result in requests {
824 match result.await {
825 Ok(_) => reloaded = true,
826 Err(err) => error!("{}", err),
827 }
828 }
829
830 if !reloaded && server.data.infos.read().should_reload(&path) {
831 if let Err(err) = server.load_internal(None, path, true, None).await {
832 error!("{}", err);
833 }
834 }
835 })
836 .detach();
837 }
838
839 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
844 pub fn add<A: Asset>(&self, asset: A) -> Handle<A> {
845 self.load_asset(LoadedAsset::new_with_dependencies(asset))
846 }
847
848 pub(crate) fn load_asset<A: Asset>(&self, asset: impl Into<LoadedAsset<A>>) -> Handle<A> {
849 let loaded_asset: LoadedAsset<A> = asset.into();
850 let erased_loaded_asset: ErasedLoadedAsset = loaded_asset.into();
851 self.load_asset_untyped(None, erased_loaded_asset)
852 .typed_debug_checked()
853 }
854
855 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
856 pub(crate) fn load_asset_untyped(
857 &self,
858 path: Option<AssetPath<'static>>,
859 asset: impl Into<ErasedLoadedAsset>,
860 ) -> UntypedHandle {
861 let loaded_asset = asset.into();
862 let handle = if let Some(path) = path {
863 let (handle, _) = self.data.infos.write().get_or_create_path_handle_erased(
864 path,
865 loaded_asset.asset_type_id(),
866 Some(loaded_asset.asset_type_name()),
867 HandleLoadingMode::NotLoading,
868 None,
869 );
870 handle
871 } else {
872 self.data.infos.write().create_loading_handle_untyped(
873 loaded_asset.asset_type_id(),
874 loaded_asset.asset_type_name(),
875 )
876 };
877 self.send_asset_event(InternalAssetEvent::Loaded {
878 id: handle.id(),
879 loaded_asset,
880 });
881 handle
882 }
883
884 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
889 pub fn add_async<A: Asset, E: core::error::Error + Send + Sync + 'static>(
890 &self,
891 future: impl Future<Output = Result<A, E>> + Send + 'static,
892 ) -> Handle<A> {
893 let mut infos = self.data.infos.write();
894 let handle =
895 infos.create_loading_handle_untyped(TypeId::of::<A>(), core::any::type_name::<A>());
896
897 #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
899 drop(infos);
900
901 let id = handle.id();
902
903 let event_sender = self.data.asset_event_sender.clone();
904
905 let task = IoTaskPool::get().spawn(async move {
906 match future.await {
907 Ok(asset) => {
908 let loaded_asset = LoadedAsset::new_with_dependencies(asset).into();
909 event_sender
910 .send(InternalAssetEvent::Loaded { id, loaded_asset })
911 .unwrap();
912 }
913 Err(error) => {
914 let error = AddAsyncError {
915 error: Arc::new(error),
916 };
917 error!("{error}");
918 event_sender
919 .send(InternalAssetEvent::Failed {
920 id,
921 path: Default::default(),
922 error: AssetLoadError::AddAsyncError(error),
923 })
924 .unwrap();
925 }
926 }
927 });
928
929 #[cfg(not(any(target_arch = "wasm32", not(feature = "multi_threaded"))))]
930 infos.pending_tasks.insert(id, task);
931
932 #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
933 task.detach();
934
935 handle.typed_debug_checked()
936 }
937
938 #[must_use = "not using the returned strong handle may result in the unexpected release of the assets"]
947 pub fn load_folder<'a>(&self, path: impl Into<AssetPath<'a>>) -> Handle<LoadedFolder> {
948 let path = path.into().into_owned();
949 let (handle, should_load) = self
950 .data
951 .infos
952 .write()
953 .get_or_create_path_handle::<LoadedFolder>(
954 path.clone(),
955 HandleLoadingMode::Request,
956 None,
957 );
958 if !should_load {
959 return handle;
960 }
961 let id = handle.id().untyped();
962 self.load_folder_internal(id, path);
963
964 handle
965 }
966
967 pub(crate) fn load_folder_internal(&self, id: UntypedAssetId, path: AssetPath) {
968 async fn load_folder<'a>(
969 source: AssetSourceId<'static>,
970 path: &'a Path,
971 reader: &'a dyn ErasedAssetReader,
972 server: &'a AssetServer,
973 handles: &'a mut Vec<UntypedHandle>,
974 ) -> Result<(), AssetLoadError> {
975 let is_dir = reader.is_directory(path).await?;
976 if is_dir {
977 let mut path_stream = reader.read_directory(path.as_ref()).await?;
978 while let Some(child_path) = path_stream.next().await {
979 if reader.is_directory(&child_path).await? {
980 Box::pin(load_folder(
981 source.clone(),
982 &child_path,
983 reader,
984 server,
985 handles,
986 ))
987 .await?;
988 } else {
989 let path = child_path.to_str().expect("Path should be a valid string.");
990 let asset_path = AssetPath::parse(path).with_source(source.clone());
991 match server.load_untyped_async(asset_path).await {
992 Ok(handle) => handles.push(handle),
993 Err(
995 AssetLoadError::MissingAssetLoaderForTypeName(_)
996 | AssetLoadError::MissingAssetLoaderForExtension(_),
997 ) => {}
998 Err(err) => return Err(err),
999 }
1000 }
1001 }
1002 }
1003 Ok(())
1004 }
1005
1006 let path = path.into_owned();
1007 let server = self.clone();
1008 IoTaskPool::get()
1009 .spawn(async move {
1010 let Ok(source) = server.get_source(path.source()) else {
1011 error!(
1012 "Failed to load {path}. AssetSource {} does not exist",
1013 path.source()
1014 );
1015 return;
1016 };
1017
1018 let asset_reader = match server.data.mode {
1019 AssetServerMode::Unprocessed => source.reader(),
1020 AssetServerMode::Processed => match source.processed_reader() {
1021 Ok(reader) => reader,
1022 Err(_) => {
1023 error!(
1024 "Failed to load {path}. AssetSource {} does not have a processed AssetReader",
1025 path.source()
1026 );
1027 return;
1028 }
1029 },
1030 };
1031
1032 let mut handles = Vec::new();
1033 match load_folder(source.id(), path.path(), asset_reader, &server, &mut handles).await {
1034 Ok(_) => server.send_asset_event(InternalAssetEvent::Loaded {
1035 id,
1036 loaded_asset: LoadedAsset::new_with_dependencies(
1037 LoadedFolder { handles },
1038 )
1039 .into(),
1040 }),
1041 Err(err) => {
1042 error!("Failed to load folder. {err}");
1043 server.send_asset_event(InternalAssetEvent::Failed { id, error: err, path });
1044 },
1045 }
1046 })
1047 .detach();
1048 }
1049
1050 fn send_asset_event(&self, event: InternalAssetEvent) {
1051 self.data.asset_event_sender.send(event).unwrap();
1052 }
1053
1054 pub fn get_load_states(
1056 &self,
1057 id: impl Into<UntypedAssetId>,
1058 ) -> Option<(LoadState, DependencyLoadState, RecursiveDependencyLoadState)> {
1059 self.data.infos.read().get(id.into()).map(|i| {
1060 (
1061 i.load_state.clone(),
1062 i.dep_load_state.clone(),
1063 i.rec_dep_load_state.clone(),
1064 )
1065 })
1066 }
1067
1068 pub fn get_load_state(&self, id: impl Into<UntypedAssetId>) -> Option<LoadState> {
1074 self.data
1075 .infos
1076 .read()
1077 .get(id.into())
1078 .map(|i| i.load_state.clone())
1079 }
1080
1081 pub fn get_dependency_load_state(
1087 &self,
1088 id: impl Into<UntypedAssetId>,
1089 ) -> Option<DependencyLoadState> {
1090 self.data
1091 .infos
1092 .read()
1093 .get(id.into())
1094 .map(|i| i.dep_load_state.clone())
1095 }
1096
1097 pub fn get_recursive_dependency_load_state(
1103 &self,
1104 id: impl Into<UntypedAssetId>,
1105 ) -> Option<RecursiveDependencyLoadState> {
1106 self.data
1107 .infos
1108 .read()
1109 .get(id.into())
1110 .map(|i| i.rec_dep_load_state.clone())
1111 }
1112
1113 pub fn load_state(&self, id: impl Into<UntypedAssetId>) -> LoadState {
1118 self.get_load_state(id).unwrap_or(LoadState::NotLoaded)
1119 }
1120
1121 pub fn dependency_load_state(&self, id: impl Into<UntypedAssetId>) -> DependencyLoadState {
1126 self.get_dependency_load_state(id)
1127 .unwrap_or(DependencyLoadState::NotLoaded)
1128 }
1129
1130 pub fn recursive_dependency_load_state(
1135 &self,
1136 id: impl Into<UntypedAssetId>,
1137 ) -> RecursiveDependencyLoadState {
1138 self.get_recursive_dependency_load_state(id)
1139 .unwrap_or(RecursiveDependencyLoadState::NotLoaded)
1140 }
1141
1142 pub fn is_loaded(&self, id: impl Into<UntypedAssetId>) -> bool {
1144 matches!(self.load_state(id), LoadState::Loaded)
1145 }
1146
1147 pub fn is_loaded_with_direct_dependencies(&self, id: impl Into<UntypedAssetId>) -> bool {
1149 matches!(
1150 self.get_load_states(id),
1151 Some((LoadState::Loaded, DependencyLoadState::Loaded, _))
1152 )
1153 }
1154
1155 pub fn is_loaded_with_dependencies(&self, id: impl Into<UntypedAssetId>) -> bool {
1158 matches!(
1159 self.get_load_states(id),
1160 Some((
1161 LoadState::Loaded,
1162 DependencyLoadState::Loaded,
1163 RecursiveDependencyLoadState::Loaded
1164 ))
1165 )
1166 }
1167
1168 pub fn get_handle<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Option<Handle<A>> {
1171 self.get_path_and_type_id_handle(&path.into(), TypeId::of::<A>())
1172 .map(UntypedHandle::typed_debug_checked)
1173 }
1174
1175 pub fn get_id_handle<A: Asset>(&self, id: AssetId<A>) -> Option<Handle<A>> {
1183 self.get_id_handle_untyped(id.untyped())
1184 .map(UntypedHandle::typed)
1185 }
1186
1187 pub fn get_id_handle_untyped(&self, id: UntypedAssetId) -> Option<UntypedHandle> {
1190 self.data.infos.read().get_id_handle(id)
1191 }
1192
1193 pub fn is_managed(&self, id: impl Into<UntypedAssetId>) -> bool {
1196 self.data.infos.read().contains_key(id.into())
1197 }
1198
1199 pub fn get_path_id<'a>(&self, path: impl Into<AssetPath<'a>>) -> Option<UntypedAssetId> {
1206 let infos = self.data.infos.read();
1207 let path = path.into();
1208 let mut ids = infos.get_path_ids(&path);
1209 ids.next()
1210 }
1211
1212 pub fn get_path_ids<'a>(&self, path: impl Into<AssetPath<'a>>) -> Vec<UntypedAssetId> {
1216 let infos = self.data.infos.read();
1217 let path = path.into();
1218 infos.get_path_ids(&path).collect()
1219 }
1220
1221 pub fn get_handle_untyped<'a>(&self, path: impl Into<AssetPath<'a>>) -> Option<UntypedHandle> {
1228 let infos = self.data.infos.read();
1229 let path = path.into();
1230 let mut handles = infos.get_path_handles(&path);
1231 handles.next()
1232 }
1233
1234 pub fn get_handles_untyped<'a>(&self, path: impl Into<AssetPath<'a>>) -> Vec<UntypedHandle> {
1238 let infos = self.data.infos.read();
1239 let path = path.into();
1240 infos.get_path_handles(&path).collect()
1241 }
1242
1243 pub fn get_path_and_type_id_handle(
1246 &self,
1247 path: &AssetPath,
1248 type_id: TypeId,
1249 ) -> Option<UntypedHandle> {
1250 let infos = self.data.infos.read();
1251 let path = path.into();
1252 infos.get_path_and_type_id_handle(&path, type_id)
1253 }
1254
1255 pub fn get_path(&self, id: impl Into<UntypedAssetId>) -> Option<AssetPath> {
1257 let infos = self.data.infos.read();
1258 let info = infos.get(id.into())?;
1259 Some(info.path.as_ref()?.clone())
1260 }
1261
1262 pub fn mode(&self) -> AssetServerMode {
1264 self.data.mode
1265 }
1266
1267 pub fn preregister_loader<L: AssetLoader>(&self, extensions: &[&str]) {
1272 self.data.loaders.write().reserve::<L>(extensions);
1273 }
1274
1275 pub(crate) fn get_or_create_path_handle<'a, A: Asset>(
1277 &self,
1278 path: impl Into<AssetPath<'a>>,
1279 meta_transform: Option<MetaTransform>,
1280 ) -> Handle<A> {
1281 let mut infos = self.data.infos.write();
1282 infos
1283 .get_or_create_path_handle::<A>(
1284 path.into().into_owned(),
1285 HandleLoadingMode::NotLoading,
1286 meta_transform,
1287 )
1288 .0
1289 }
1290
1291 pub(crate) fn get_or_create_path_handle_erased<'a>(
1296 &self,
1297 path: impl Into<AssetPath<'a>>,
1298 type_id: TypeId,
1299 meta_transform: Option<MetaTransform>,
1300 ) -> UntypedHandle {
1301 let mut infos = self.data.infos.write();
1302 infos
1303 .get_or_create_path_handle_erased(
1304 path.into().into_owned(),
1305 type_id,
1306 None,
1307 HandleLoadingMode::NotLoading,
1308 meta_transform,
1309 )
1310 .0
1311 }
1312
1313 pub(crate) async fn get_meta_loader_and_reader<'a>(
1314 &'a self,
1315 asset_path: &'a AssetPath<'_>,
1316 asset_type_id: Option<TypeId>,
1317 ) -> Result<
1318 (
1319 Box<dyn AssetMetaDyn>,
1320 Arc<dyn ErasedAssetLoader>,
1321 Box<dyn Reader + 'a>,
1322 ),
1323 AssetLoadError,
1324 > {
1325 let source = self.get_source(asset_path.source())?;
1326 let asset_reader = match self.data.mode {
1331 AssetServerMode::Unprocessed => source.reader(),
1332 AssetServerMode::Processed => source.processed_reader()?,
1333 };
1334 let reader = asset_reader.read(asset_path.path()).await?;
1335 let read_meta = match &self.data.meta_check {
1336 AssetMetaCheck::Always => true,
1337 AssetMetaCheck::Paths(paths) => paths.contains(asset_path),
1338 AssetMetaCheck::Never => false,
1339 };
1340
1341 if read_meta {
1342 match asset_reader.read_meta_bytes(asset_path.path()).await {
1343 Ok(meta_bytes) => {
1344 let minimal: AssetMetaMinimal =
1346 ron::de::from_bytes(&meta_bytes).map_err(|e| {
1347 AssetLoadError::DeserializeMeta {
1348 path: asset_path.clone_owned(),
1349 error: DeserializeMetaError::DeserializeMinimal(e).into(),
1350 }
1351 })?;
1352 let loader_name = match minimal.asset {
1353 AssetActionMinimal::Load { loader } => loader,
1354 AssetActionMinimal::Process { .. } => {
1355 return Err(AssetLoadError::CannotLoadProcessedAsset {
1356 path: asset_path.clone_owned(),
1357 })
1358 }
1359 AssetActionMinimal::Ignore => {
1360 return Err(AssetLoadError::CannotLoadIgnoredAsset {
1361 path: asset_path.clone_owned(),
1362 })
1363 }
1364 };
1365 let loader = self.get_asset_loader_with_type_name(&loader_name).await?;
1366 let meta = loader.deserialize_meta(&meta_bytes).map_err(|e| {
1367 AssetLoadError::DeserializeMeta {
1368 path: asset_path.clone_owned(),
1369 error: e.into(),
1370 }
1371 })?;
1372
1373 Ok((meta, loader, reader))
1374 }
1375 Err(AssetReaderError::NotFound(_)) => {
1376 let loader = {
1378 self.data
1379 .loaders
1380 .read()
1381 .find(None, asset_type_id, None, Some(asset_path))
1382 };
1383
1384 let error = || AssetLoadError::MissingAssetLoader {
1385 loader_name: None,
1386 asset_type_id,
1387 extension: None,
1388 asset_path: Some(asset_path.to_string()),
1389 };
1390
1391 let loader = loader.ok_or_else(error)?.get().await.map_err(|_| error())?;
1392
1393 let meta = loader.default_meta();
1394 Ok((meta, loader, reader))
1395 }
1396 Err(err) => Err(err.into()),
1397 }
1398 } else {
1399 let loader = {
1400 self.data
1401 .loaders
1402 .read()
1403 .find(None, asset_type_id, None, Some(asset_path))
1404 };
1405
1406 let error = || AssetLoadError::MissingAssetLoader {
1407 loader_name: None,
1408 asset_type_id,
1409 extension: None,
1410 asset_path: Some(asset_path.to_string()),
1411 };
1412
1413 let loader = loader.ok_or_else(error)?.get().await.map_err(|_| error())?;
1414
1415 let meta = loader.default_meta();
1416 Ok((meta, loader, reader))
1417 }
1418 }
1419
1420 pub(crate) async fn load_with_meta_loader_and_reader(
1421 &self,
1422 asset_path: &AssetPath<'_>,
1423 meta: &dyn AssetMetaDyn,
1424 loader: &dyn ErasedAssetLoader,
1425 reader: &mut dyn Reader,
1426 load_dependencies: bool,
1427 populate_hashes: bool,
1428 ) -> Result<ErasedLoadedAsset, AssetLoadError> {
1429 let asset_path = asset_path.clone_owned();
1431 let load_context =
1432 LoadContext::new(self, asset_path.clone(), load_dependencies, populate_hashes);
1433 AssertUnwindSafe(loader.load(reader, meta, load_context))
1434 .catch_unwind()
1435 .await
1436 .map_err(|_| AssetLoadError::AssetLoaderPanic {
1437 path: asset_path.clone_owned(),
1438 loader_name: loader.type_name(),
1439 })?
1440 .map_err(|e| {
1441 AssetLoadError::AssetLoaderError(AssetLoaderError {
1442 path: asset_path.clone_owned(),
1443 loader_name: loader.type_name(),
1444 error: e.into(),
1445 })
1446 })
1447 }
1448
1449 pub async fn wait_for_asset<A: Asset>(
1457 &self,
1458 handle: &Handle<A>,
1461 ) -> Result<(), WaitForAssetError> {
1462 self.wait_for_asset_id(handle.id().untyped()).await
1463 }
1464
1465 pub async fn wait_for_asset_untyped(
1473 &self,
1474 handle: &UntypedHandle,
1477 ) -> Result<(), WaitForAssetError> {
1478 self.wait_for_asset_id(handle.id()).await
1479 }
1480
1481 pub async fn wait_for_asset_id(
1501 &self,
1502 id: impl Into<UntypedAssetId>,
1503 ) -> Result<(), WaitForAssetError> {
1504 let id = id.into();
1505 core::future::poll_fn(move |cx| self.wait_for_asset_id_poll_fn(cx, id)).await
1506 }
1507
1508 fn wait_for_asset_id_poll_fn(
1510 &self,
1511 cx: &mut core::task::Context<'_>,
1512 id: UntypedAssetId,
1513 ) -> Poll<Result<(), WaitForAssetError>> {
1514 let infos = self.data.infos.read();
1515
1516 let Some(info) = infos.get(id) else {
1517 return Poll::Ready(Err(WaitForAssetError::NotLoaded));
1518 };
1519
1520 match (&info.load_state, &info.rec_dep_load_state) {
1521 (LoadState::Loaded, RecursiveDependencyLoadState::Loaded) => Poll::Ready(Ok(())),
1522 (LoadState::NotLoaded, _) => Poll::Ready(Err(WaitForAssetError::NotLoaded)),
1524 (LoadState::Loading, _)
1526 | (_, RecursiveDependencyLoadState::Loading)
1527 | (LoadState::Loaded, RecursiveDependencyLoadState::NotLoaded) => {
1528 let has_waker = info
1530 .waiting_tasks
1531 .iter()
1532 .any(|waker| waker.will_wake(cx.waker()));
1533
1534 if has_waker {
1535 return Poll::Pending;
1536 }
1537
1538 let mut infos = {
1539 drop(infos);
1541 self.data.infos.write()
1542 };
1543
1544 let Some(info) = infos.get_mut(id) else {
1545 return Poll::Ready(Err(WaitForAssetError::NotLoaded));
1546 };
1547
1548 let is_loading = matches!(
1551 (&info.load_state, &info.rec_dep_load_state),
1552 (LoadState::Loading, _)
1553 | (_, RecursiveDependencyLoadState::Loading)
1554 | (LoadState::Loaded, RecursiveDependencyLoadState::NotLoaded)
1555 );
1556
1557 if !is_loading {
1558 cx.waker().wake_by_ref();
1559 } else {
1560 info.waiting_tasks.push(cx.waker().clone());
1562 }
1563
1564 Poll::Pending
1565 }
1566 (LoadState::Failed(error), _) => {
1567 Poll::Ready(Err(WaitForAssetError::Failed(error.clone())))
1568 }
1569 (_, RecursiveDependencyLoadState::Failed(error)) => {
1570 Poll::Ready(Err(WaitForAssetError::DependencyFailed(error.clone())))
1571 }
1572 }
1573 }
1574
1575 pub async fn write_default_loader_meta_file_for_path(
1586 &self,
1587 path: impl Into<AssetPath<'_>>,
1588 ) -> Result<(), WriteDefaultMetaError> {
1589 let path = path.into();
1590 let loader = self.get_path_asset_loader(&path).await?;
1591
1592 let meta = loader.default_meta();
1593 let serialized_meta = meta.serialize();
1594
1595 let source = self.get_source(path.source())?;
1596
1597 let reader = source.reader();
1598 match reader.read_meta_bytes(path.path()).await {
1599 Ok(_) => return Err(WriteDefaultMetaError::MetaAlreadyExists),
1600 Err(AssetReaderError::NotFound(_)) => {
1601 }
1603 Err(AssetReaderError::Io(err)) => {
1604 return Err(WriteDefaultMetaError::IoErrorFromExistingMetaCheck(err))
1605 }
1606 Err(AssetReaderError::HttpError(err)) => {
1607 return Err(WriteDefaultMetaError::HttpErrorFromExistingMetaCheck(err))
1608 }
1609 }
1610
1611 let writer = source.writer()?;
1612 writer
1613 .write_meta_bytes(path.path(), &serialized_meta)
1614 .await?;
1615
1616 Ok(())
1617 }
1618}
1619
1620pub fn handle_internal_asset_events(world: &mut World) {
1622 world.resource_scope(|world, server: Mut<AssetServer>| {
1623 let mut infos = server.data.infos.write();
1624 let var_name = vec![];
1625 let mut untyped_failures = var_name;
1626 for event in server.data.asset_event_receiver.try_iter() {
1627 match event {
1628 InternalAssetEvent::Loaded { id, loaded_asset } => {
1629 infos.process_asset_load(
1630 id,
1631 loaded_asset,
1632 world,
1633 &server.data.asset_event_sender,
1634 );
1635 }
1636 InternalAssetEvent::LoadedWithDependencies { id } => {
1637 let sender = infos
1638 .dependency_loaded_event_sender
1639 .get(&id.type_id())
1640 .expect("Asset event sender should exist");
1641 sender(world, id);
1642 if let Some(info) = infos.get_mut(id) {
1643 for waker in info.waiting_tasks.drain(..) {
1644 waker.wake();
1645 }
1646 }
1647 }
1648 InternalAssetEvent::Failed { id, path, error } => {
1649 infos.process_asset_fail(id, error.clone());
1650
1651 untyped_failures.push(UntypedAssetLoadFailedEvent {
1653 id,
1654 path: path.clone(),
1655 error: error.clone(),
1656 });
1657
1658 let sender = infos
1660 .dependency_failed_event_sender
1661 .get(&id.type_id())
1662 .expect("Asset failed event sender should exist");
1663 sender(world, id, path, error);
1664 }
1665 }
1666 }
1667
1668 if !untyped_failures.is_empty() {
1669 world.send_event_batch(untyped_failures);
1670 }
1671
1672 fn queue_ancestors(
1673 asset_path: &AssetPath,
1674 infos: &AssetInfos,
1675 paths_to_reload: &mut HashSet<AssetPath<'static>>,
1676 ) {
1677 if let Some(dependents) = infos.loader_dependents.get(asset_path) {
1678 for dependent in dependents {
1679 paths_to_reload.insert(dependent.to_owned());
1680 queue_ancestors(dependent, infos, paths_to_reload);
1681 }
1682 }
1683 }
1684
1685 let reload_parent_folders = |path: PathBuf, source: &AssetSourceId<'static>| {
1686 let mut current_folder = path;
1687 while let Some(parent) = current_folder.parent() {
1688 current_folder = parent.to_path_buf();
1689 let parent_asset_path =
1690 AssetPath::from(current_folder.clone()).with_source(source.clone());
1691 for folder_handle in infos.get_path_handles(&parent_asset_path) {
1692 info!("Reloading folder {parent_asset_path} because the content has changed");
1693 server.load_folder_internal(folder_handle.id(), parent_asset_path.clone());
1694 }
1695 }
1696 };
1697
1698 let mut paths_to_reload = <HashSet<_>>::default();
1699 let mut handle_event = |source: AssetSourceId<'static>, event: AssetSourceEvent| {
1700 match event {
1701 AssetSourceEvent::ModifiedAsset(path) | AssetSourceEvent::ModifiedMeta(path) => {
1704 let path = AssetPath::from(path).with_source(source);
1705 queue_ancestors(&path, &infos, &mut paths_to_reload);
1706 paths_to_reload.insert(path);
1707 }
1708 AssetSourceEvent::RenamedFolder { old, new } => {
1709 reload_parent_folders(old, &source);
1710 reload_parent_folders(new, &source);
1711 }
1712 AssetSourceEvent::AddedAsset(path)
1713 | AssetSourceEvent::RemovedAsset(path)
1714 | AssetSourceEvent::RemovedFolder(path)
1715 | AssetSourceEvent::AddedFolder(path) => {
1716 reload_parent_folders(path, &source);
1717 }
1718 _ => {}
1719 }
1720 };
1721
1722 for source in server.data.sources.iter() {
1723 match server.data.mode {
1724 AssetServerMode::Unprocessed => {
1725 if let Some(receiver) = source.event_receiver() {
1726 for event in receiver.try_iter() {
1727 handle_event(source.id(), event);
1728 }
1729 }
1730 }
1731 AssetServerMode::Processed => {
1732 if let Some(receiver) = source.processed_event_receiver() {
1733 for event in receiver.try_iter() {
1734 handle_event(source.id(), event);
1735 }
1736 }
1737 }
1738 }
1739 }
1740
1741 for path in paths_to_reload {
1742 info!("Reloading {path} because it has changed");
1743 server.reload(path);
1744 }
1745
1746 #[cfg(not(any(target_arch = "wasm32", not(feature = "multi_threaded"))))]
1747 infos
1748 .pending_tasks
1749 .retain(|_, load_task| !load_task.is_finished());
1750 });
1751}
1752
1753pub(crate) enum InternalAssetEvent {
1755 Loaded {
1756 id: UntypedAssetId,
1757 loaded_asset: ErasedLoadedAsset,
1758 },
1759 LoadedWithDependencies {
1760 id: UntypedAssetId,
1761 },
1762 Failed {
1763 id: UntypedAssetId,
1764 path: AssetPath<'static>,
1765 error: AssetLoadError,
1766 },
1767}
1768
1769#[derive(Component, Clone, Debug)]
1771pub enum LoadState {
1772 NotLoaded,
1774
1775 Loading,
1777
1778 Loaded,
1780
1781 Failed(Arc<AssetLoadError>),
1785}
1786
1787impl LoadState {
1788 pub fn is_loading(&self) -> bool {
1790 matches!(self, Self::Loading)
1791 }
1792
1793 pub fn is_loaded(&self) -> bool {
1795 matches!(self, Self::Loaded)
1796 }
1797
1798 pub fn is_failed(&self) -> bool {
1800 matches!(self, Self::Failed(_))
1801 }
1802}
1803
1804#[derive(Component, Clone, Debug)]
1806pub enum DependencyLoadState {
1807 NotLoaded,
1809
1810 Loading,
1812
1813 Loaded,
1815
1816 Failed(Arc<AssetLoadError>),
1820}
1821
1822impl DependencyLoadState {
1823 pub fn is_loading(&self) -> bool {
1825 matches!(self, Self::Loading)
1826 }
1827
1828 pub fn is_loaded(&self) -> bool {
1830 matches!(self, Self::Loaded)
1831 }
1832
1833 pub fn is_failed(&self) -> bool {
1835 matches!(self, Self::Failed(_))
1836 }
1837}
1838
1839#[derive(Component, Clone, Debug)]
1841pub enum RecursiveDependencyLoadState {
1842 NotLoaded,
1844
1845 Loading,
1847
1848 Loaded,
1850
1851 Failed(Arc<AssetLoadError>),
1856}
1857
1858impl RecursiveDependencyLoadState {
1859 pub fn is_loading(&self) -> bool {
1861 matches!(self, Self::Loading)
1862 }
1863
1864 pub fn is_loaded(&self) -> bool {
1866 matches!(self, Self::Loaded)
1867 }
1868
1869 pub fn is_failed(&self) -> bool {
1871 matches!(self, Self::Failed(_))
1872 }
1873}
1874
1875#[derive(Error, Debug, Clone)]
1877#[expect(
1878 missing_docs,
1879 reason = "Adding docs to the variants would not add information beyond the error message and the names"
1880)]
1881pub enum AssetLoadError {
1882 #[error("Requested handle of type {requested:?} for asset '{path}' does not match actual asset type '{actual_asset_name}', which used loader '{loader_name}'")]
1883 RequestedHandleTypeMismatch {
1884 path: AssetPath<'static>,
1885 requested: TypeId,
1886 actual_asset_name: &'static str,
1887 loader_name: &'static str,
1888 },
1889 #[error("Could not find an asset loader matching: Loader Name: {loader_name:?}; Asset Type: {loader_name:?}; Extension: {extension:?}; Path: {asset_path:?};")]
1890 MissingAssetLoader {
1891 loader_name: Option<String>,
1892 asset_type_id: Option<TypeId>,
1893 extension: Option<String>,
1894 asset_path: Option<String>,
1895 },
1896 #[error(transparent)]
1897 MissingAssetLoaderForExtension(#[from] MissingAssetLoaderForExtensionError),
1898 #[error(transparent)]
1899 MissingAssetLoaderForTypeName(#[from] MissingAssetLoaderForTypeNameError),
1900 #[error(transparent)]
1901 MissingAssetLoaderForTypeIdError(#[from] MissingAssetLoaderForTypeIdError),
1902 #[error(transparent)]
1903 AssetReaderError(#[from] AssetReaderError),
1904 #[error(transparent)]
1905 MissingAssetSourceError(#[from] MissingAssetSourceError),
1906 #[error(transparent)]
1907 MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError),
1908 #[error("Encountered an error while reading asset metadata bytes")]
1909 AssetMetaReadError,
1910 #[error("Failed to deserialize meta for asset {path}: {error}")]
1911 DeserializeMeta {
1912 path: AssetPath<'static>,
1913 error: Box<DeserializeMetaError>,
1914 },
1915 #[error("Asset '{path}' is configured to be processed. It cannot be loaded directly.")]
1916 #[from(ignore)]
1917 CannotLoadProcessedAsset { path: AssetPath<'static> },
1918 #[error("Asset '{path}' is configured to be ignored. It cannot be loaded.")]
1919 #[from(ignore)]
1920 CannotLoadIgnoredAsset { path: AssetPath<'static> },
1921 #[error("Failed to load asset '{path}', asset loader '{loader_name}' panicked")]
1922 AssetLoaderPanic {
1923 path: AssetPath<'static>,
1924 loader_name: &'static str,
1925 },
1926 #[error(transparent)]
1927 AssetLoaderError(#[from] AssetLoaderError),
1928 #[error(transparent)]
1929 AddAsyncError(#[from] AddAsyncError),
1930 #[error("The file at '{}' does not contain the labeled asset '{}'; it contains the following {} assets: {}",
1931 base_path,
1932 label,
1933 all_labels.len(),
1934 all_labels.iter().map(|l| format!("'{}'", l)).collect::<Vec<_>>().join(", "))]
1935 MissingLabel {
1936 base_path: AssetPath<'static>,
1937 label: String,
1938 all_labels: Vec<String>,
1939 },
1940}
1941
1942#[derive(Error, Debug, Clone)]
1944#[error("Failed to load asset '{path}' with asset loader '{loader_name}': {error}")]
1945pub struct AssetLoaderError {
1946 path: AssetPath<'static>,
1947 loader_name: &'static str,
1948 error: Arc<dyn core::error::Error + Send + Sync + 'static>,
1949}
1950
1951impl AssetLoaderError {
1952 pub fn path(&self) -> &AssetPath<'static> {
1954 &self.path
1955 }
1956}
1957
1958#[derive(Error, Debug, Clone)]
1960#[error("An error occurred while resolving an asset added by `add_async`: {error}")]
1961pub struct AddAsyncError {
1962 error: Arc<dyn core::error::Error + Send + Sync + 'static>,
1963}
1964
1965#[derive(Error, Debug, Clone, PartialEq, Eq)]
1967#[error("no `AssetLoader` found{}", format_missing_asset_ext(extensions))]
1968pub struct MissingAssetLoaderForExtensionError {
1969 extensions: Vec<String>,
1970}
1971
1972#[derive(Error, Debug, Clone, PartialEq, Eq)]
1974#[error("no `AssetLoader` found with the name '{type_name}'")]
1975pub struct MissingAssetLoaderForTypeNameError {
1976 pub type_name: String,
1978}
1979
1980#[derive(Error, Debug, Clone, PartialEq, Eq)]
1982#[error("no `AssetLoader` found with the ID '{type_id:?}'")]
1983pub struct MissingAssetLoaderForTypeIdError {
1984 pub type_id: TypeId,
1986}
1987
1988fn format_missing_asset_ext(exts: &[String]) -> String {
1989 if !exts.is_empty() {
1990 format!(
1991 " for the following extension{}: {}",
1992 if exts.len() > 1 { "s" } else { "" },
1993 exts.join(", ")
1994 )
1995 } else {
1996 " for file with no extension".to_string()
1997 }
1998}
1999
2000impl core::fmt::Debug for AssetServer {
2001 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2002 f.debug_struct("AssetServer")
2003 .field("info", &self.data.infos.read())
2004 .finish()
2005 }
2006}
2007
2008const UNTYPED_SOURCE_SUFFIX: &str = "--untyped";
2011
2012#[derive(Error, Debug, Clone)]
2014pub enum WaitForAssetError {
2015 #[error("tried to wait for an asset that is not being loaded")]
2017 NotLoaded,
2018 #[error(transparent)]
2020 Failed(Arc<AssetLoadError>),
2021 #[error(transparent)]
2023 DependencyFailed(Arc<AssetLoadError>),
2024}
2025
2026#[derive(Error, Debug)]
2027pub enum WriteDefaultMetaError {
2028 #[error(transparent)]
2029 MissingAssetLoader(#[from] MissingAssetLoaderForExtensionError),
2030 #[error(transparent)]
2031 MissingAssetSource(#[from] MissingAssetSourceError),
2032 #[error(transparent)]
2033 MissingAssetWriter(#[from] MissingAssetWriterError),
2034 #[error("failed to write default asset meta file: {0}")]
2035 FailedToWriteMeta(#[from] AssetWriterError),
2036 #[error("asset meta file already exists, so avoiding overwrite")]
2037 MetaAlreadyExists,
2038 #[error("encountered an I/O error while reading the existing meta file: {0}")]
2039 IoErrorFromExistingMetaCheck(Arc<std::io::Error>),
2040 #[error("encountered HTTP status {0} when reading the existing meta file")]
2041 HttpErrorFromExistingMetaCheck(u16),
2042}