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, AssetIndex, AssetLoadFailedEvent,
18 AssetMetaCheck, Assets, DeserializeMetaError, ErasedAssetIndex, ErasedLoadedAsset, Handle,
19 LoadedUntypedAsset, UnapprovedPathMode, UntypedAssetId, UntypedAssetLoadFailedEvent,
20 UntypedHandle,
21};
22use alloc::{borrow::ToOwned, boxed::Box, vec, vec::Vec};
23use alloc::{
24 format,
25 string::{String, ToString},
26 sync::Arc,
27};
28use atomicow::CowArc;
29use bevy_diagnostic::{DiagnosticPath, Diagnostics};
30use bevy_ecs::prelude::*;
31use bevy_platform::{
32 collections::HashSet,
33 sync::{PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard},
34};
35use bevy_tasks::IoTaskPool;
36use core::{any::TypeId, future::Future, panic::AssertUnwindSafe, task::Poll};
37use crossbeam_channel::{Receiver, Sender};
38use either::Either;
39use futures_lite::{FutureExt, StreamExt};
40use info::*;
41use loaders::*;
42use std::path::{Path, PathBuf};
43use thiserror::Error;
44use tracing::{error, info};
45
46#[derive(Resource, Clone)]
62pub struct AssetServer {
63 pub(crate) data: Arc<AssetServerData>,
64}
65
66pub(crate) struct AssetServerData {
68 pub(crate) infos: RwLock<AssetInfos>,
69 pub(crate) loaders: Arc<RwLock<AssetLoaders>>,
70 asset_event_sender: Sender<InternalAssetEvent>,
71 asset_event_receiver: Receiver<InternalAssetEvent>,
72 sources: Arc<AssetSources>,
73 mode: AssetServerMode,
74 meta_check: AssetMetaCheck,
75 unapproved_path_mode: UnapprovedPathMode,
76}
77
78#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80pub enum AssetServerMode {
81 Unprocessed,
83 Processed,
85}
86
87impl AssetServer {
88 pub const STARTED_LOAD_COUNT: DiagnosticPath = DiagnosticPath::const_new("started_load_count");
90
91 pub fn new(
94 sources: Arc<AssetSources>,
95 mode: AssetServerMode,
96 watching_for_changes: bool,
97 unapproved_path_mode: UnapprovedPathMode,
98 ) -> Self {
99 Self::new_with_loaders(
100 sources,
101 Default::default(),
102 mode,
103 AssetMetaCheck::Always,
104 watching_for_changes,
105 unapproved_path_mode,
106 )
107 }
108
109 pub fn new_with_meta_check(
112 sources: Arc<AssetSources>,
113 mode: AssetServerMode,
114 meta_check: AssetMetaCheck,
115 watching_for_changes: bool,
116 unapproved_path_mode: UnapprovedPathMode,
117 ) -> Self {
118 Self::new_with_loaders(
119 sources,
120 Default::default(),
121 mode,
122 meta_check,
123 watching_for_changes,
124 unapproved_path_mode,
125 )
126 }
127
128 pub(crate) fn new_with_loaders(
129 sources: Arc<AssetSources>,
130 loaders: Arc<RwLock<AssetLoaders>>,
131 mode: AssetServerMode,
132 meta_check: AssetMetaCheck,
133 watching_for_changes: bool,
134 unapproved_path_mode: UnapprovedPathMode,
135 ) -> Self {
136 let (asset_event_sender, asset_event_receiver) = crossbeam_channel::unbounded();
137 let mut infos = AssetInfos::default();
138 infos.watching_for_changes = watching_for_changes;
139 Self {
140 data: Arc::new(AssetServerData {
141 sources,
142 mode,
143 meta_check,
144 asset_event_sender,
145 asset_event_receiver,
146 loaders,
147 infos: RwLock::new(infos),
148 unapproved_path_mode,
149 }),
150 }
151 }
152
153 pub(crate) fn read_infos(&self) -> RwLockReadGuard<'_, AssetInfos> {
154 self.data
155 .infos
156 .read()
157 .unwrap_or_else(PoisonError::into_inner)
158 }
159
160 pub(crate) fn write_infos(&self) -> RwLockWriteGuard<'_, AssetInfos> {
161 self.data
162 .infos
163 .write()
164 .unwrap_or_else(PoisonError::into_inner)
165 }
166
167 fn read_loaders(&self) -> RwLockReadGuard<'_, AssetLoaders> {
168 self.data
169 .loaders
170 .read()
171 .unwrap_or_else(PoisonError::into_inner)
172 }
173
174 fn write_loaders(&self) -> RwLockWriteGuard<'_, AssetLoaders> {
175 self.data
176 .loaders
177 .write()
178 .unwrap_or_else(PoisonError::into_inner)
179 }
180
181 pub fn get_source<'a>(
183 &self,
184 source: impl Into<AssetSourceId<'a>>,
185 ) -> Result<&AssetSource, MissingAssetSourceError> {
186 self.data.sources.get(source.into())
187 }
188
189 pub fn watching_for_changes(&self) -> bool {
191 self.read_infos().watching_for_changes
192 }
193
194 pub fn register_loader<L: AssetLoader>(&self, loader: L) {
196 self.write_loaders().push(loader);
197 }
198
199 pub fn register_asset<A: Asset>(&self, assets: &Assets<A>) {
201 self.register_handle_provider(assets.get_handle_provider());
202 fn sender<A: Asset>(world: &mut World, index: AssetIndex) {
203 world
204 .resource_mut::<Messages<AssetEvent<A>>>()
205 .write(AssetEvent::LoadedWithDependencies { id: index.into() });
206 }
207 fn failed_sender<A: Asset>(
208 world: &mut World,
209 index: AssetIndex,
210 path: AssetPath<'static>,
211 error: AssetLoadError,
212 ) {
213 world
214 .resource_mut::<Messages<AssetLoadFailedEvent<A>>>()
215 .write(AssetLoadFailedEvent {
216 id: index.into(),
217 path,
218 error,
219 });
220 }
221
222 let mut infos = self.write_infos();
223
224 infos
225 .dependency_loaded_event_sender
226 .insert(TypeId::of::<A>(), sender::<A>);
227
228 infos
229 .dependency_failed_event_sender
230 .insert(TypeId::of::<A>(), failed_sender::<A>);
231 }
232
233 pub(crate) fn register_handle_provider(&self, handle_provider: AssetHandleProvider) {
234 self.write_infos()
235 .handle_providers
236 .insert(handle_provider.type_id, handle_provider);
237 }
238
239 pub async fn get_asset_loader_with_extension(
241 &self,
242 extension: &str,
243 ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForExtensionError> {
244 let error = || MissingAssetLoaderForExtensionError {
245 extensions: vec![extension.to_string()],
246 };
247
248 let loader = self
249 .read_loaders()
250 .get_by_extension(extension)
251 .ok_or_else(error)?;
252 loader.get().await.map_err(|_| error())
253 }
254
255 pub async fn get_asset_loader_with_type_name(
257 &self,
258 type_name: &str,
259 ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForTypeNameError> {
260 let error = || MissingAssetLoaderForTypeNameError {
261 type_name: type_name.to_string(),
262 };
263
264 let loader = self
265 .read_loaders()
266 .get_by_name(type_name)
267 .ok_or_else(error)?;
268 loader.get().await.map_err(|_| error())
269 }
270
271 pub async fn get_path_asset_loader<'a>(
273 &self,
274 path: impl Into<AssetPath<'a>>,
275 ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForExtensionError> {
276 let path = path.into();
277
278 let error = || {
279 let Some(full_extension) = path.get_full_extension() else {
280 return MissingAssetLoaderForExtensionError {
281 extensions: Vec::new(),
282 };
283 };
284
285 let mut extensions = vec![full_extension.clone()];
286 extensions.extend(
287 AssetPath::iter_secondary_extensions(&full_extension).map(ToString::to_string),
288 );
289
290 MissingAssetLoaderForExtensionError { extensions }
291 };
292
293 let loader = self.read_loaders().get_by_path(&path).ok_or_else(error)?;
294 loader.get().await.map_err(|_| error())
295 }
296
297 pub async fn get_asset_loader_with_asset_type_id(
299 &self,
300 type_id: TypeId,
301 ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForTypeIdError> {
302 let error = || MissingAssetLoaderForTypeIdError { type_id };
303
304 let loader = self.read_loaders().get_by_type(type_id).ok_or_else(error)?;
305 loader.get().await.map_err(|_| error())
306 }
307
308 pub async fn get_asset_loader_with_asset_type<A: Asset>(
310 &self,
311 ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForTypeIdError> {
312 self.get_asset_loader_with_asset_type_id(TypeId::of::<A>())
313 .await
314 }
315
316 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
360 pub fn load<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Handle<A> {
361 self.load_with_meta_transform(path, None, (), false)
362 }
363
364 pub fn load_override<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Handle<A> {
370 self.load_with_meta_transform(path, None, (), true)
371 }
372
373 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
389 pub fn load_acquire<'a, A: Asset, G: Send + Sync + 'static>(
390 &self,
391 path: impl Into<AssetPath<'a>>,
392 guard: G,
393 ) -> Handle<A> {
394 self.load_with_meta_transform(path, None, guard, false)
395 }
396
397 pub fn load_acquire_override<'a, A: Asset, G: Send + Sync + 'static>(
403 &self,
404 path: impl Into<AssetPath<'a>>,
405 guard: G,
406 ) -> Handle<A> {
407 self.load_with_meta_transform(path, None, guard, true)
408 }
409
410 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
414 pub fn load_with_settings<'a, A: Asset, S: Settings>(
415 &self,
416 path: impl Into<AssetPath<'a>>,
417 settings: impl Fn(&mut S) + Send + Sync + 'static,
418 ) -> Handle<A> {
419 self.load_with_meta_transform(
420 path,
421 Some(loader_settings_meta_transform(settings)),
422 (),
423 false,
424 )
425 }
426
427 pub fn load_with_settings_override<'a, A: Asset, S: Settings>(
433 &self,
434 path: impl Into<AssetPath<'a>>,
435 settings: impl Fn(&mut S) + Send + Sync + 'static,
436 ) -> Handle<A> {
437 self.load_with_meta_transform(
438 path,
439 Some(loader_settings_meta_transform(settings)),
440 (),
441 true,
442 )
443 }
444
445 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
455 pub fn load_acquire_with_settings<'a, A: Asset, S: Settings, G: Send + Sync + 'static>(
456 &self,
457 path: impl Into<AssetPath<'a>>,
458 settings: impl Fn(&mut S) + Send + Sync + 'static,
459 guard: G,
460 ) -> Handle<A> {
461 self.load_with_meta_transform(
462 path,
463 Some(loader_settings_meta_transform(settings)),
464 guard,
465 false,
466 )
467 }
468
469 pub fn load_acquire_with_settings_override<
475 'a,
476 A: Asset,
477 S: Settings,
478 G: Send + Sync + 'static,
479 >(
480 &self,
481 path: impl Into<AssetPath<'a>>,
482 settings: impl Fn(&mut S) + Send + Sync + 'static,
483 guard: G,
484 ) -> Handle<A> {
485 self.load_with_meta_transform(
486 path,
487 Some(loader_settings_meta_transform(settings)),
488 guard,
489 true,
490 )
491 }
492
493 pub(crate) fn load_with_meta_transform<'a, A: Asset, G: Send + Sync + 'static>(
494 &self,
495 path: impl Into<AssetPath<'a>>,
496 meta_transform: Option<MetaTransform>,
497 guard: G,
498 override_unapproved: bool,
499 ) -> Handle<A> {
500 let path = path.into().into_owned();
501
502 if path.is_unapproved() {
503 match (&self.data.unapproved_path_mode, override_unapproved) {
504 (UnapprovedPathMode::Allow, _) | (UnapprovedPathMode::Deny, true) => {}
505 (UnapprovedPathMode::Deny, false) | (UnapprovedPathMode::Forbid, _) => {
506 error!("Asset path {path} is unapproved. See UnapprovedPathMode for details.");
507 return Handle::default();
508 }
509 }
510 }
511
512 let mut infos = self.write_infos();
513 let (handle, should_load) = infos.get_or_create_path_handle::<A>(
514 path.clone(),
515 HandleLoadingMode::Request,
516 meta_transform,
517 );
518
519 if should_load {
520 self.spawn_load_task(handle.clone().untyped(), path, infos, guard);
521 }
522
523 handle
524 }
525
526 pub(crate) fn load_erased_with_meta_transform<'a, G: Send + Sync + 'static>(
527 &self,
528 path: impl Into<AssetPath<'a>>,
529 type_id: TypeId,
530 meta_transform: Option<MetaTransform>,
531 guard: G,
532 ) -> UntypedHandle {
533 let path = path.into().into_owned();
534 let mut infos = self.write_infos();
535 let (handle, should_load) = infos.get_or_create_path_handle_erased(
536 path.clone(),
537 type_id,
538 None,
539 HandleLoadingMode::Request,
540 meta_transform,
541 );
542
543 if should_load {
544 self.spawn_load_task(handle.clone(), path, infos, guard);
545 }
546
547 handle
548 }
549
550 pub(crate) fn spawn_load_task<G: Send + Sync + 'static>(
551 &self,
552 handle: UntypedHandle,
553 path: AssetPath<'static>,
554 mut infos: RwLockWriteGuard<AssetInfos>,
555 guard: G,
556 ) {
557 infos.stats.started_load_tasks += 1;
558
559 #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
561 drop(infos);
562
563 let owned_handle = handle.clone();
564 let server = self.clone();
565 let task = IoTaskPool::get().spawn(async move {
566 if let Err(err) = server
567 .load_internal(Some(owned_handle), path, false, None)
568 .await
569 {
570 error!("{}", err);
571 }
572 drop(guard);
573 });
574
575 #[cfg(not(any(target_arch = "wasm32", not(feature = "multi_threaded"))))]
576 {
577 let mut infos = infos;
578 infos
579 .pending_tasks
580 .insert((&handle).try_into().unwrap(), task);
581 }
582
583 #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
584 task.detach();
585 }
586
587 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
591 pub async fn load_untyped_async<'a>(
592 &self,
593 path: impl Into<AssetPath<'a>>,
594 ) -> Result<UntypedHandle, AssetLoadError> {
595 self.write_infos().stats.started_load_tasks += 1;
596
597 let path: AssetPath = path.into();
598 self.load_internal(None, path, false, None)
599 .await
600 .map(|h| h.expect("handle must be returned, since we didn't pass in an input handle"))
601 }
602
603 pub(crate) fn load_unknown_type_with_meta_transform<'a>(
604 &self,
605 path: impl Into<AssetPath<'a>>,
606 meta_transform: Option<MetaTransform>,
607 ) -> Handle<LoadedUntypedAsset> {
608 let path = path.into().into_owned();
609 let untyped_source = AssetSourceId::Name(match path.source() {
610 AssetSourceId::Default => CowArc::Static(UNTYPED_SOURCE_SUFFIX),
611 AssetSourceId::Name(source) => {
612 CowArc::Owned(format!("{source}--{UNTYPED_SOURCE_SUFFIX}").into())
613 }
614 });
615 let mut infos = self.write_infos();
616 let (handle, should_load) = infos.get_or_create_path_handle::<LoadedUntypedAsset>(
617 path.clone().with_source(untyped_source),
618 HandleLoadingMode::Request,
619 meta_transform,
620 );
621
622 if !should_load {
623 return handle;
624 }
625 let index = (&handle).try_into().unwrap();
626
627 infos.stats.started_load_tasks += 1;
628
629 #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
631 drop(infos);
632
633 let server = self.clone();
634 let task = IoTaskPool::get().spawn(async move {
635 let path_clone = path.clone();
636 match server
637 .load_internal(None, path, false, None)
638 .await
639 .map(|h| {
640 h.expect("handle must be returned, since we didn't pass in an input handle")
641 }) {
642 Ok(handle) => server.send_asset_event(InternalAssetEvent::Loaded {
643 index,
644 loaded_asset: LoadedAsset::new_with_dependencies(LoadedUntypedAsset { handle })
645 .into(),
646 }),
647 Err(err) => {
648 error!("{err}");
649 server.send_asset_event(InternalAssetEvent::Failed {
650 index,
651 path: path_clone,
652 error: err,
653 });
654 }
655 };
656 });
657
658 #[cfg(not(any(target_arch = "wasm32", not(feature = "multi_threaded"))))]
659 infos.pending_tasks.insert(index, task);
660
661 #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
662 task.detach();
663
664 handle
665 }
666
667 #[must_use = "not using the returned strong handle may result in the unexpected release of the assets"]
691 pub fn load_untyped<'a>(&self, path: impl Into<AssetPath<'a>>) -> Handle<LoadedUntypedAsset> {
692 self.load_unknown_type_with_meta_transform(path, None)
693 }
694
695 async fn load_internal<'a>(
704 &self,
705 input_handle: Option<UntypedHandle>,
706 path: AssetPath<'a>,
707 force: bool,
708 meta_transform: Option<MetaTransform>,
709 ) -> Result<Option<UntypedHandle>, AssetLoadError> {
710 let input_handle_type_id = input_handle.as_ref().map(UntypedHandle::type_id);
711
712 let path = path.into_owned();
713 let path_clone = path.clone();
714 let (mut meta, loader, mut reader) = self
715 .get_meta_loader_and_reader(&path_clone, input_handle_type_id)
716 .await
717 .inspect_err(|e| {
718 if let Some(handle) = &input_handle {
721 self.send_asset_event(InternalAssetEvent::Failed {
722 index: handle.try_into().unwrap(),
723 path: path.clone_owned(),
724 error: e.clone(),
725 });
726 }
727 })?;
728
729 if let Some(meta_transform) = input_handle.as_ref().and_then(|h| h.meta_transform()) {
730 (*meta_transform)(&mut *meta);
731 }
732
733 let asset_id: Option<ErasedAssetIndex>; let fetched_handle; let should_load; if let Some(input_handle) = input_handle {
737 asset_id = Some((&input_handle).try_into().unwrap());
740 fetched_handle = None;
743 should_load = true;
745 } else {
746 let mut infos = self.write_infos();
752 let result = infos.get_or_create_path_handle_internal(
753 path.clone(),
754 path.label().is_none().then(|| loader.asset_type_id()),
755 HandleLoadingMode::Request,
756 meta_transform,
757 );
758 match unwrap_with_context(result, Either::Left(loader.asset_type_name())) {
759 None => {
762 asset_id = None;
765 fetched_handle = None;
766 should_load = true;
769 }
770 Some((handle, result_should_load)) => {
771 asset_id = Some((&handle).try_into().unwrap());
774 fetched_handle = Some(handle);
775 should_load = result_should_load;
776 }
777 }
778 }
779 if let Some(asset_type_id) = asset_id.map(|id| id.type_id) {
781 if path.label().is_none() && asset_type_id != loader.asset_type_id() {
784 error!(
785 "Expected {:?}, got {:?}",
786 asset_type_id,
787 loader.asset_type_id()
788 );
789 return Err(AssetLoadError::RequestedHandleTypeMismatch {
790 path: path.into_owned(),
791 requested: asset_type_id,
792 actual_asset_name: loader.asset_type_name(),
793 loader_name: loader.type_path(),
794 });
795 }
796 }
797 if !should_load && !force {
799 return Ok(fetched_handle);
800 }
801
802 let (base_asset_id, _base_handle, base_path) = if path.label().is_some() {
806 let mut infos = self.write_infos();
807 let base_path = path.without_label().into_owned();
808 let base_handle = infos
809 .get_or_create_path_handle_erased(
810 base_path.clone(),
811 loader.asset_type_id(),
812 Some(loader.asset_type_name()),
813 HandleLoadingMode::Force,
814 None,
815 )
816 .0;
817 (
818 (&base_handle).try_into().unwrap(),
821 Some(base_handle),
822 base_path,
823 )
824 } else {
825 (asset_id.unwrap(), None, path.clone())
826 };
827
828 match self
829 .load_with_settings_loader_and_reader(
830 &base_path,
831 meta.loader_settings().expect("meta is set to Load"),
832 &*loader,
833 &mut *reader,
834 true,
835 false,
836 )
837 .await
838 {
839 Ok(loaded_asset) => {
840 let final_handle = if let Some(label) = path.label_cow() {
841 match loaded_asset.labeled_assets.get(&label) {
842 Some(labeled_asset) => Some(labeled_asset.handle.clone()),
843 None => {
844 let mut all_labels: Vec<String> = loaded_asset
845 .labeled_assets
846 .keys()
847 .map(|s| (**s).to_owned())
848 .collect();
849 all_labels.sort_unstable();
850 return Err(AssetLoadError::MissingLabel {
851 base_path,
852 label: label.to_string(),
853 all_labels,
854 });
855 }
856 }
857 } else {
858 fetched_handle
859 };
860
861 self.send_asset_event(InternalAssetEvent::Loaded {
862 index: base_asset_id,
863 loaded_asset,
864 });
865 Ok(final_handle)
866 }
867 Err(err) => {
868 self.send_asset_event(InternalAssetEvent::Failed {
869 index: base_asset_id,
870 error: err.clone(),
871 path: path.into_owned(),
872 });
873 Err(err)
874 }
875 }
876 }
877
878 pub fn reload<'a>(&self, path: impl Into<AssetPath<'a>>) {
880 self.reload_internal(path, false);
881 }
882
883 fn reload_internal<'a>(&self, path: impl Into<AssetPath<'a>>, log: bool) {
884 let server = self.clone();
885 let path = path.into().into_owned();
886 IoTaskPool::get()
887 .spawn(async move {
888 let mut reloaded = false;
889
890 let requests = server
891 .read_infos()
892 .get_path_handles(&path)
893 .map(|handle| server.load_internal(Some(handle), path.clone(), true, None))
894 .collect::<Vec<_>>();
895
896 for result in requests {
897 server.write_infos().stats.started_load_tasks += 1;
899 match result.await {
900 Ok(_) => reloaded = true,
901 Err(err) => error!("{}", err),
902 }
903 }
904
905 if !reloaded && server.read_infos().should_reload(&path) {
906 server.write_infos().stats.started_load_tasks += 1;
907 match server.load_internal(None, path.clone(), true, None).await {
908 Ok(_) => reloaded = true,
909 Err(err) => error!("{}", err),
910 }
911 }
912
913 if log && reloaded {
914 info!("Reloaded {}", path);
915 }
916 })
917 .detach();
918 }
919
920 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
925 pub fn add<A: Asset>(&self, asset: A) -> Handle<A> {
926 self.load_asset(LoadedAsset::new_with_dependencies(asset))
927 }
928
929 pub(crate) fn load_asset<A: Asset>(&self, asset: impl Into<LoadedAsset<A>>) -> Handle<A> {
930 let loaded_asset: LoadedAsset<A> = asset.into();
931 let erased_loaded_asset: ErasedLoadedAsset = loaded_asset.into();
932 self.load_asset_untyped(None, erased_loaded_asset)
933 .typed_debug_checked()
934 }
935
936 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
937 pub(crate) fn load_asset_untyped(
938 &self,
939 path: Option<AssetPath<'static>>,
940 asset: impl Into<ErasedLoadedAsset>,
941 ) -> UntypedHandle {
942 let loaded_asset = asset.into();
943 let handle = if let Some(path) = path {
944 let (handle, _) = self.write_infos().get_or_create_path_handle_erased(
945 path,
946 loaded_asset.asset_type_id(),
947 Some(loaded_asset.asset_type_name()),
948 HandleLoadingMode::NotLoading,
949 None,
950 );
951 handle
952 } else {
953 self.write_infos().create_loading_handle_untyped(
954 loaded_asset.asset_type_id(),
955 loaded_asset.asset_type_name(),
956 )
957 };
958 self.send_asset_event(InternalAssetEvent::Loaded {
959 index: (&handle).try_into().unwrap(),
961 loaded_asset,
962 });
963 handle
964 }
965
966 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
971 pub fn add_async<A: Asset, E: core::error::Error + Send + Sync + 'static>(
972 &self,
973 future: impl Future<Output = Result<A, E>> + Send + 'static,
974 ) -> Handle<A> {
975 let mut infos = self.write_infos();
976 let handle =
977 infos.create_loading_handle_untyped(TypeId::of::<A>(), core::any::type_name::<A>());
978
979 #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
981 drop(infos);
982
983 let index = (&handle).try_into().unwrap();
985
986 let event_sender = self.data.asset_event_sender.clone();
987
988 let task = IoTaskPool::get().spawn(async move {
989 match future.await {
990 Ok(asset) => {
991 let loaded_asset = LoadedAsset::new_with_dependencies(asset).into();
992 event_sender
993 .send(InternalAssetEvent::Loaded {
994 index,
995 loaded_asset,
996 })
997 .unwrap();
998 }
999 Err(error) => {
1000 let error = AddAsyncError {
1001 error: Arc::new(error),
1002 };
1003 error!("{error}");
1004 event_sender
1005 .send(InternalAssetEvent::Failed {
1006 index,
1007 path: Default::default(),
1008 error: AssetLoadError::AddAsyncError(error),
1009 })
1010 .unwrap();
1011 }
1012 }
1013 });
1014
1015 #[cfg(not(any(target_arch = "wasm32", not(feature = "multi_threaded"))))]
1016 infos.pending_tasks.insert(index, task);
1017
1018 #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
1019 task.detach();
1020
1021 handle.typed_debug_checked()
1022 }
1023
1024 #[must_use = "not using the returned strong handle may result in the unexpected release of the assets"]
1033 pub fn load_folder<'a>(&self, path: impl Into<AssetPath<'a>>) -> Handle<LoadedFolder> {
1034 let path = path.into().into_owned();
1035 let (handle, should_load) = self
1036 .write_infos()
1037 .get_or_create_path_handle::<LoadedFolder>(
1038 path.clone(),
1039 HandleLoadingMode::Request,
1040 None,
1041 );
1042 if !should_load {
1043 return handle;
1044 }
1045 let index = (&handle).try_into().unwrap();
1047 self.load_folder_internal(index, path);
1048
1049 handle
1050 }
1051
1052 pub(crate) fn load_folder_internal(&self, index: ErasedAssetIndex, path: AssetPath) {
1053 async fn load_folder<'a>(
1054 source: AssetSourceId<'static>,
1055 path: &'a Path,
1056 reader: &'a dyn ErasedAssetReader,
1057 server: &'a AssetServer,
1058 handles: &'a mut Vec<UntypedHandle>,
1059 ) -> Result<(), AssetLoadError> {
1060 let is_dir = reader.is_directory(path).await?;
1061 if is_dir {
1062 let mut path_stream = reader.read_directory(path.as_ref()).await?;
1063 while let Some(child_path) = path_stream.next().await {
1064 if reader.is_directory(&child_path).await? {
1065 Box::pin(load_folder(
1066 source.clone(),
1067 &child_path,
1068 reader,
1069 server,
1070 handles,
1071 ))
1072 .await?;
1073 } else {
1074 let path = child_path.to_str().expect("Path should be a valid string.");
1075 let asset_path = AssetPath::parse(path).with_source(source.clone());
1076 match server.load_untyped_async(asset_path).await {
1077 Ok(handle) => handles.push(handle),
1078 Err(
1080 AssetLoadError::MissingAssetLoaderForTypeName(_)
1081 | AssetLoadError::MissingAssetLoaderForExtension(_),
1082 ) => {}
1083 Err(err) => return Err(err),
1084 }
1085 }
1086 }
1087 }
1088 Ok(())
1089 }
1090
1091 self.write_infos().stats.started_load_tasks += 1;
1092
1093 let path = path.into_owned();
1094 let server = self.clone();
1095 IoTaskPool::get()
1096 .spawn(async move {
1097 let Ok(source) = server.get_source(path.source()) else {
1098 error!(
1099 "Failed to load {path}. AssetSource {} does not exist",
1100 path.source()
1101 );
1102 return;
1103 };
1104
1105 let asset_reader = match server.data.mode {
1106 AssetServerMode::Unprocessed => source.reader(),
1107 AssetServerMode::Processed => match source.processed_reader() {
1108 Ok(reader) => reader,
1109 Err(_) => {
1110 error!(
1111 "Failed to load {path}. AssetSource {} does not have a processed AssetReader",
1112 path.source()
1113 );
1114 return;
1115 }
1116 },
1117 };
1118
1119 let mut handles = Vec::new();
1120 match load_folder(source.id(), path.path(), asset_reader, &server, &mut handles).await {
1121 Ok(_) => server.send_asset_event(InternalAssetEvent::Loaded {
1122 index,
1123 loaded_asset: LoadedAsset::new_with_dependencies(
1124 LoadedFolder { handles },
1125 )
1126 .into(),
1127 }),
1128 Err(err) => {
1129 error!("Failed to load folder. {err}");
1130 server.send_asset_event(InternalAssetEvent::Failed { index, error: err, path });
1131 },
1132 }
1133 })
1134 .detach();
1135 }
1136
1137 fn send_asset_event(&self, event: InternalAssetEvent) {
1138 self.data.asset_event_sender.send(event).unwrap();
1139 }
1140
1141 pub fn get_load_states(
1143 &self,
1144 id: impl Into<UntypedAssetId>,
1145 ) -> Option<(LoadState, DependencyLoadState, RecursiveDependencyLoadState)> {
1146 let Ok(index) = id.into().try_into() else {
1147 return None;
1149 };
1150 self.read_infos().get(index).map(|i| {
1151 (
1152 i.load_state.clone(),
1153 i.dep_load_state.clone(),
1154 i.rec_dep_load_state.clone(),
1155 )
1156 })
1157 }
1158
1159 pub fn get_load_state(&self, id: impl Into<UntypedAssetId>) -> Option<LoadState> {
1165 let Ok(index) = id.into().try_into() else {
1166 return None;
1168 };
1169 self.read_infos().get(index).map(|i| i.load_state.clone())
1170 }
1171
1172 pub fn get_dependency_load_state(
1178 &self,
1179 id: impl Into<UntypedAssetId>,
1180 ) -> Option<DependencyLoadState> {
1181 let Ok(index) = id.into().try_into() else {
1182 return None;
1184 };
1185 self.read_infos()
1186 .get(index)
1187 .map(|i| i.dep_load_state.clone())
1188 }
1189
1190 pub fn get_recursive_dependency_load_state(
1196 &self,
1197 id: impl Into<UntypedAssetId>,
1198 ) -> Option<RecursiveDependencyLoadState> {
1199 let Ok(index) = id.into().try_into() else {
1200 return None;
1202 };
1203 self.read_infos()
1204 .get(index)
1205 .map(|i| i.rec_dep_load_state.clone())
1206 }
1207
1208 pub fn load_state(&self, id: impl Into<UntypedAssetId>) -> LoadState {
1213 self.get_load_state(id).unwrap_or(LoadState::NotLoaded)
1214 }
1215
1216 pub fn dependency_load_state(&self, id: impl Into<UntypedAssetId>) -> DependencyLoadState {
1221 self.get_dependency_load_state(id)
1222 .unwrap_or(DependencyLoadState::NotLoaded)
1223 }
1224
1225 pub fn recursive_dependency_load_state(
1230 &self,
1231 id: impl Into<UntypedAssetId>,
1232 ) -> RecursiveDependencyLoadState {
1233 self.get_recursive_dependency_load_state(id)
1234 .unwrap_or(RecursiveDependencyLoadState::NotLoaded)
1235 }
1236
1237 pub fn is_loaded(&self, id: impl Into<UntypedAssetId>) -> bool {
1239 matches!(self.load_state(id), LoadState::Loaded)
1240 }
1241
1242 pub fn is_loaded_with_direct_dependencies(&self, id: impl Into<UntypedAssetId>) -> bool {
1244 matches!(
1245 self.get_load_states(id),
1246 Some((LoadState::Loaded, DependencyLoadState::Loaded, _))
1247 )
1248 }
1249
1250 pub fn is_loaded_with_dependencies(&self, id: impl Into<UntypedAssetId>) -> bool {
1253 matches!(
1254 self.get_load_states(id),
1255 Some((
1256 LoadState::Loaded,
1257 DependencyLoadState::Loaded,
1258 RecursiveDependencyLoadState::Loaded
1259 ))
1260 )
1261 }
1262
1263 pub fn get_handle<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Option<Handle<A>> {
1266 self.get_path_and_type_id_handle(&path.into(), TypeId::of::<A>())
1267 .map(UntypedHandle::typed_debug_checked)
1268 }
1269
1270 pub fn get_id_handle<A: Asset>(&self, id: AssetId<A>) -> Option<Handle<A>> {
1278 self.get_id_handle_untyped(id.untyped())
1279 .map(UntypedHandle::typed)
1280 }
1281
1282 pub fn get_id_handle_untyped(&self, id: UntypedAssetId) -> Option<UntypedHandle> {
1285 let Ok(index) = id.try_into() else {
1286 return None;
1288 };
1289 self.read_infos().get_index_handle(index)
1290 }
1291
1292 pub fn is_managed(&self, id: impl Into<UntypedAssetId>) -> bool {
1295 let Ok(index) = id.into().try_into() else {
1296 return false;
1298 };
1299 self.read_infos().contains_key(index)
1300 }
1301
1302 pub fn get_path_id<'a>(&self, path: impl Into<AssetPath<'a>>) -> Option<UntypedAssetId> {
1309 let infos = self.read_infos();
1310 let path = path.into();
1311 let mut ids = infos.get_path_indices(&path);
1312 ids.next().map(Into::into)
1313 }
1314
1315 pub fn get_path_ids<'a>(&self, path: impl Into<AssetPath<'a>>) -> Vec<UntypedAssetId> {
1319 let path = path.into();
1320 self.read_infos()
1321 .get_path_indices(&path)
1322 .map(Into::into)
1323 .collect()
1324 }
1325
1326 pub fn get_handle_untyped<'a>(&self, path: impl Into<AssetPath<'a>>) -> Option<UntypedHandle> {
1333 let path = path.into();
1334 self.read_infos().get_path_handles(&path).next()
1335 }
1336
1337 pub fn get_handles_untyped<'a>(&self, path: impl Into<AssetPath<'a>>) -> Vec<UntypedHandle> {
1341 let path = path.into();
1342 self.read_infos().get_path_handles(&path).collect()
1343 }
1344
1345 pub fn get_path_and_type_id_handle(
1348 &self,
1349 path: &AssetPath,
1350 type_id: TypeId,
1351 ) -> Option<UntypedHandle> {
1352 let path = path.into();
1353 self.read_infos()
1354 .get_path_and_type_id_handle(&path, type_id)
1355 }
1356
1357 pub fn get_path(&self, id: impl Into<UntypedAssetId>) -> Option<AssetPath<'_>> {
1359 let Ok(index) = id.into().try_into() else {
1360 return None;
1362 };
1363 let infos = self.read_infos();
1364 let info = infos.get(index)?;
1365 Some(info.path.as_ref()?.clone())
1366 }
1367
1368 pub fn mode(&self) -> AssetServerMode {
1370 self.data.mode
1371 }
1372
1373 pub fn preregister_loader<L: AssetLoader>(&self, extensions: &[&str]) {
1378 self.write_loaders().reserve::<L>(extensions);
1379 }
1380
1381 pub(crate) fn get_or_create_path_handle<'a, A: Asset>(
1383 &self,
1384 path: impl Into<AssetPath<'a>>,
1385 meta_transform: Option<MetaTransform>,
1386 ) -> Handle<A> {
1387 self.write_infos()
1388 .get_or_create_path_handle::<A>(
1389 path.into().into_owned(),
1390 HandleLoadingMode::NotLoading,
1391 meta_transform,
1392 )
1393 .0
1394 }
1395
1396 pub(crate) fn get_or_create_path_handle_erased<'a>(
1401 &self,
1402 path: impl Into<AssetPath<'a>>,
1403 type_id: TypeId,
1404 meta_transform: Option<MetaTransform>,
1405 ) -> UntypedHandle {
1406 self.write_infos()
1407 .get_or_create_path_handle_erased(
1408 path.into().into_owned(),
1409 type_id,
1410 None,
1411 HandleLoadingMode::NotLoading,
1412 meta_transform,
1413 )
1414 .0
1415 }
1416
1417 pub(crate) async fn get_meta_loader_and_reader<'a>(
1418 &'a self,
1419 asset_path: &'a AssetPath<'_>,
1420 asset_type_id: Option<TypeId>,
1421 ) -> Result<
1422 (
1423 Box<dyn AssetMetaDyn>,
1424 Arc<dyn ErasedAssetLoader>,
1425 Box<dyn Reader + 'a>,
1426 ),
1427 AssetLoadError,
1428 > {
1429 let source = self.get_source(asset_path.source())?;
1430 let asset_reader = match self.data.mode {
1431 AssetServerMode::Unprocessed => source.reader(),
1432 AssetServerMode::Processed => source.processed_reader()?,
1433 };
1434 let read_meta = match &self.data.meta_check {
1435 AssetMetaCheck::Always => true,
1436 AssetMetaCheck::Paths(paths) => paths.contains(asset_path),
1437 AssetMetaCheck::Never => false,
1438 };
1439
1440 let mut meta_reader;
1445
1446 let (meta, loader) = if read_meta {
1447 match asset_reader.read_meta(asset_path.path()).await {
1448 Ok(new_meta_reader) => {
1449 meta_reader = new_meta_reader;
1450 let mut meta_bytes = vec![];
1451 meta_reader
1452 .read_to_end(&mut meta_bytes)
1453 .await
1454 .map_err(|err| AssetLoadError::AssetReaderError(err.into()))?;
1455 let minimal: AssetMetaMinimal =
1457 ron::de::from_bytes(&meta_bytes).map_err(|e| {
1458 AssetLoadError::DeserializeMeta {
1459 path: asset_path.clone_owned(),
1460 error: DeserializeMetaError::DeserializeMinimal(e).into(),
1461 }
1462 })?;
1463 let loader_name = match minimal.asset {
1464 AssetActionMinimal::Load { loader } => loader,
1465 AssetActionMinimal::Process { .. } => {
1466 return Err(AssetLoadError::CannotLoadProcessedAsset {
1467 path: asset_path.clone_owned(),
1468 })
1469 }
1470 AssetActionMinimal::Ignore => {
1471 return Err(AssetLoadError::CannotLoadIgnoredAsset {
1472 path: asset_path.clone_owned(),
1473 })
1474 }
1475 };
1476 let loader = self.get_asset_loader_with_type_name(&loader_name).await?;
1477 let meta = loader.deserialize_meta(&meta_bytes).map_err(|e| {
1478 AssetLoadError::DeserializeMeta {
1479 path: asset_path.clone_owned(),
1480 error: e.into(),
1481 }
1482 })?;
1483
1484 (meta, loader)
1485 }
1486 Err(AssetReaderError::NotFound(_)) => {
1487 let loader = {
1489 self.read_loaders()
1490 .find(None, asset_type_id, None, Some(asset_path))
1491 };
1492
1493 let error = || AssetLoadError::MissingAssetLoader {
1494 loader_name: None,
1495 asset_type_id,
1496 extension: None,
1497 asset_path: Some(asset_path.to_string()),
1498 };
1499
1500 let loader = loader.ok_or_else(error)?.get().await.map_err(|_| error())?;
1501
1502 let meta = loader.default_meta();
1503 (meta, loader)
1504 }
1505 Err(err) => return Err(err.into()),
1506 }
1507 } else {
1508 let loader = {
1509 self.read_loaders()
1510 .find(None, asset_type_id, None, Some(asset_path))
1511 };
1512
1513 let error = || AssetLoadError::MissingAssetLoader {
1514 loader_name: None,
1515 asset_type_id,
1516 extension: None,
1517 asset_path: Some(asset_path.to_string()),
1518 };
1519
1520 let loader = loader.ok_or_else(error)?.get().await.map_err(|_| error())?;
1521
1522 let meta = loader.default_meta();
1523 (meta, loader)
1524 };
1525 let required_features =
1526 loader.reader_required_features(meta.loader_settings().expect("meta specifies load"));
1527 let reader = asset_reader
1528 .read(asset_path.path(), required_features)
1529 .await?;
1530 Ok((meta, loader, reader))
1531 }
1532
1533 pub(crate) async fn load_with_settings_loader_and_reader(
1534 &self,
1535 asset_path: &AssetPath<'_>,
1536 settings: &dyn Settings,
1537 loader: &dyn ErasedAssetLoader,
1538 reader: &mut dyn Reader,
1539 load_dependencies: bool,
1540 populate_hashes: bool,
1541 ) -> Result<ErasedLoadedAsset, AssetLoadError> {
1542 let asset_path = asset_path.clone_owned();
1544 let load_context =
1545 LoadContext::new(self, asset_path.clone(), load_dependencies, populate_hashes);
1546 AssertUnwindSafe(loader.load(reader, settings, load_context))
1547 .catch_unwind()
1548 .await
1549 .map_err(|_| AssetLoadError::AssetLoaderPanic {
1550 path: asset_path.clone_owned(),
1551 loader_name: loader.type_path(),
1552 })?
1553 .map_err(|e| {
1554 AssetLoadError::AssetLoaderError(AssetLoaderError {
1555 path: asset_path.clone_owned(),
1556 loader_name: loader.type_path(),
1557 error: e.into(),
1558 })
1559 })
1560 }
1561
1562 pub async fn wait_for_asset<A: Asset>(
1570 &self,
1571 handle: &Handle<A>,
1574 ) -> Result<(), WaitForAssetError> {
1575 self.wait_for_asset_id(handle.id().untyped()).await
1576 }
1577
1578 pub async fn wait_for_asset_untyped(
1586 &self,
1587 handle: &UntypedHandle,
1590 ) -> Result<(), WaitForAssetError> {
1591 self.wait_for_asset_id(handle.id()).await
1592 }
1593
1594 pub async fn wait_for_asset_id(
1614 &self,
1615 id: impl Into<UntypedAssetId>,
1616 ) -> Result<(), WaitForAssetError> {
1617 let Ok(index) = id.into().try_into() else {
1618 return Err(WaitForAssetError::NotLoaded);
1620 };
1621 core::future::poll_fn(move |cx| self.wait_for_asset_id_poll_fn(cx, index)).await
1622 }
1623
1624 fn wait_for_asset_id_poll_fn(
1626 &self,
1627 cx: &mut core::task::Context<'_>,
1628 index: ErasedAssetIndex,
1629 ) -> Poll<Result<(), WaitForAssetError>> {
1630 let infos = self.read_infos();
1631
1632 let Some(info) = infos.get(index) else {
1633 return Poll::Ready(Err(WaitForAssetError::NotLoaded));
1634 };
1635
1636 match (&info.load_state, &info.rec_dep_load_state) {
1637 (LoadState::Loaded, RecursiveDependencyLoadState::Loaded) => Poll::Ready(Ok(())),
1638 (LoadState::NotLoaded, _) => Poll::Ready(Err(WaitForAssetError::NotLoaded)),
1640 (LoadState::Loading, _)
1642 | (_, RecursiveDependencyLoadState::Loading)
1643 | (LoadState::Loaded, RecursiveDependencyLoadState::NotLoaded) => {
1644 let has_waker = info
1646 .waiting_tasks
1647 .iter()
1648 .any(|waker| waker.will_wake(cx.waker()));
1649
1650 if has_waker {
1651 return Poll::Pending;
1652 }
1653
1654 let mut infos = {
1655 drop(infos);
1657 self.write_infos()
1658 };
1659
1660 let Some(info) = infos.get_mut(index) else {
1661 return Poll::Ready(Err(WaitForAssetError::NotLoaded));
1662 };
1663
1664 let is_loading = matches!(
1667 (&info.load_state, &info.rec_dep_load_state),
1668 (LoadState::Loading, _)
1669 | (_, RecursiveDependencyLoadState::Loading)
1670 | (LoadState::Loaded, RecursiveDependencyLoadState::NotLoaded)
1671 );
1672
1673 if !is_loading {
1674 cx.waker().wake_by_ref();
1675 } else {
1676 info.waiting_tasks.push(cx.waker().clone());
1678 }
1679
1680 Poll::Pending
1681 }
1682 (LoadState::Failed(error), _) => {
1683 Poll::Ready(Err(WaitForAssetError::Failed(error.clone())))
1684 }
1685 (_, RecursiveDependencyLoadState::Failed(error)) => {
1686 Poll::Ready(Err(WaitForAssetError::DependencyFailed(error.clone())))
1687 }
1688 }
1689 }
1690
1691 pub async fn write_default_loader_meta_file_for_path(
1702 &self,
1703 path: impl Into<AssetPath<'_>>,
1704 ) -> Result<(), WriteDefaultMetaError> {
1705 let path = path.into();
1706 let loader = self.get_path_asset_loader(&path).await?;
1707
1708 let meta = loader.default_meta();
1709 let serialized_meta = meta.serialize();
1710
1711 let source = self.get_source(path.source())?;
1712
1713 let reader = source.reader();
1714 match reader.read_meta_bytes(path.path()).await {
1715 Ok(_) => return Err(WriteDefaultMetaError::MetaAlreadyExists),
1716 Err(AssetReaderError::UnsupportedFeature(feature)) => panic!("reading the meta file never requests a feature, but the following feature is unsupported: {feature}"),
1717 Err(AssetReaderError::NotFound(_)) => {
1718 }
1720 Err(AssetReaderError::Io(err)) => {
1721 return Err(WriteDefaultMetaError::IoErrorFromExistingMetaCheck(err))
1722 }
1723 Err(AssetReaderError::HttpError(err)) => {
1724 return Err(WriteDefaultMetaError::HttpErrorFromExistingMetaCheck(err))
1725 }
1726 }
1727
1728 let writer = source.writer()?;
1729 writer
1730 .write_meta_bytes(path.path(), &serialized_meta)
1731 .await?;
1732
1733 Ok(())
1734 }
1735}
1736
1737pub fn handle_internal_asset_events(world: &mut World) {
1739 world.resource_scope(|world, server: Mut<AssetServer>| {
1740 let mut infos = server.write_infos();
1741 let var_name = vec![];
1742 let mut untyped_failures = var_name;
1743 for event in server.data.asset_event_receiver.try_iter() {
1744 match event {
1745 InternalAssetEvent::Loaded {
1746 index,
1747 loaded_asset,
1748 } => {
1749 infos.process_asset_load(
1750 index,
1751 loaded_asset,
1752 world,
1753 &server.data.asset_event_sender,
1754 );
1755 }
1756 InternalAssetEvent::LoadedWithDependencies { index } => {
1757 let sender = infos
1758 .dependency_loaded_event_sender
1759 .get(&index.type_id)
1760 .expect("Asset event sender should exist");
1761 sender(world, index.index);
1762 if let Some(info) = infos.get_mut(index) {
1763 for waker in info.waiting_tasks.drain(..) {
1764 waker.wake();
1765 }
1766 }
1767 }
1768 InternalAssetEvent::Failed { index, path, error } => {
1769 infos.process_asset_fail(index, error.clone());
1770
1771 untyped_failures.push(UntypedAssetLoadFailedEvent {
1773 id: index.into(),
1774 path: path.clone(),
1775 error: error.clone(),
1776 });
1777
1778 let sender = infos
1780 .dependency_failed_event_sender
1781 .get(&index.type_id)
1782 .expect("Asset failed event sender should exist");
1783 sender(world, index.index, path, error);
1784 }
1785 }
1786 }
1787
1788 if !untyped_failures.is_empty() {
1789 world.write_message_batch(untyped_failures);
1790 }
1791
1792 if !infos.watching_for_changes {
1795 return;
1796 }
1797
1798 fn queue_ancestors(
1799 asset_path: &AssetPath,
1800 infos: &AssetInfos,
1801 paths_to_reload: &mut HashSet<AssetPath<'static>>,
1802 ) {
1803 if let Some(dependents) = infos.loader_dependents.get(asset_path) {
1804 for dependent in dependents {
1805 paths_to_reload.insert(dependent.to_owned());
1806 queue_ancestors(dependent, infos, paths_to_reload);
1807 }
1808 }
1809 }
1810
1811 let reload_parent_folders = |path: &PathBuf, source: &AssetSourceId<'static>| {
1812 for parent in path.ancestors().skip(1) {
1813 let parent_asset_path =
1814 AssetPath::from(parent.to_path_buf()).with_source(source.clone());
1815 for folder_handle in infos.get_path_handles(&parent_asset_path) {
1816 info!("Reloading folder {parent_asset_path} because the content has changed");
1817 let index = (&folder_handle).try_into().unwrap();
1819 server.load_folder_internal(index, parent_asset_path.clone());
1820 }
1821 }
1822 };
1823
1824 let mut paths_to_reload = <HashSet<_>>::default();
1825 let mut reload_path = |path: PathBuf, source: &AssetSourceId<'static>| {
1826 let path = AssetPath::from(path).with_source(source);
1827 queue_ancestors(&path, &infos, &mut paths_to_reload);
1828 paths_to_reload.insert(path);
1829 };
1830
1831 let mut handle_event = |source: AssetSourceId<'static>, event: AssetSourceEvent| {
1832 match event {
1833 AssetSourceEvent::AddedAsset(path) => {
1834 reload_parent_folders(&path, &source);
1835 reload_path(path, &source);
1836 }
1837 AssetSourceEvent::ModifiedAsset(path) | AssetSourceEvent::ModifiedMeta(path) => {
1840 reload_path(path, &source);
1841 }
1842 AssetSourceEvent::RenamedFolder { old, new } => {
1843 reload_parent_folders(&old, &source);
1844 reload_parent_folders(&new, &source);
1845 }
1846 AssetSourceEvent::RemovedAsset(path)
1847 | AssetSourceEvent::RemovedFolder(path)
1848 | AssetSourceEvent::AddedFolder(path) => {
1849 reload_parent_folders(&path, &source);
1850 }
1851 _ => {}
1852 }
1853 };
1854
1855 for source in server.data.sources.iter() {
1856 match server.data.mode {
1857 AssetServerMode::Unprocessed => {
1858 if let Some(receiver) = source.event_receiver() {
1859 while let Ok(event) = receiver.try_recv() {
1860 handle_event(source.id(), event);
1861 }
1862 }
1863 }
1864 AssetServerMode::Processed => {
1865 if let Some(receiver) = source.processed_event_receiver() {
1866 while let Ok(event) = receiver.try_recv() {
1867 handle_event(source.id(), event);
1868 }
1869 }
1870 }
1871 }
1872 }
1873
1874 #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
1877 drop(infos);
1878
1879 for path in paths_to_reload {
1880 server.reload_internal(path, true);
1881 }
1882
1883 #[cfg(not(any(target_arch = "wasm32", not(feature = "multi_threaded"))))]
1884 infos
1885 .pending_tasks
1886 .retain(|_, load_task| !load_task.is_finished());
1887 });
1888}
1889
1890pub fn publish_asset_server_diagnostics(
1892 asset_server: Res<AssetServer>,
1893 mut diagnostics: Diagnostics,
1894) {
1895 let infos = asset_server.read_infos();
1896 diagnostics.add_measurement(&AssetServer::STARTED_LOAD_COUNT, || {
1897 infos.stats.started_load_tasks as _
1898 });
1899}
1900
1901pub(crate) enum InternalAssetEvent {
1903 Loaded {
1904 index: ErasedAssetIndex,
1905 loaded_asset: ErasedLoadedAsset,
1906 },
1907 LoadedWithDependencies {
1908 index: ErasedAssetIndex,
1909 },
1910 Failed {
1911 index: ErasedAssetIndex,
1912 path: AssetPath<'static>,
1913 error: AssetLoadError,
1914 },
1915}
1916
1917#[derive(Component, Clone, Debug)]
1919pub enum LoadState {
1920 NotLoaded,
1922
1923 Loading,
1925
1926 Loaded,
1928
1929 Failed(Arc<AssetLoadError>),
1933}
1934
1935impl LoadState {
1936 pub fn is_loading(&self) -> bool {
1938 matches!(self, Self::Loading)
1939 }
1940
1941 pub fn is_loaded(&self) -> bool {
1943 matches!(self, Self::Loaded)
1944 }
1945
1946 pub fn is_failed(&self) -> bool {
1948 matches!(self, Self::Failed(_))
1949 }
1950}
1951
1952#[derive(Component, Clone, Debug)]
1954pub enum DependencyLoadState {
1955 NotLoaded,
1957
1958 Loading,
1960
1961 Loaded,
1963
1964 Failed(Arc<AssetLoadError>),
1968}
1969
1970impl DependencyLoadState {
1971 pub fn is_loading(&self) -> bool {
1973 matches!(self, Self::Loading)
1974 }
1975
1976 pub fn is_loaded(&self) -> bool {
1978 matches!(self, Self::Loaded)
1979 }
1980
1981 pub fn is_failed(&self) -> bool {
1983 matches!(self, Self::Failed(_))
1984 }
1985}
1986
1987#[derive(Component, Clone, Debug)]
1989pub enum RecursiveDependencyLoadState {
1990 NotLoaded,
1992
1993 Loading,
1995
1996 Loaded,
1998
1999 Failed(Arc<AssetLoadError>),
2004}
2005
2006impl RecursiveDependencyLoadState {
2007 pub fn is_loading(&self) -> bool {
2009 matches!(self, Self::Loading)
2010 }
2011
2012 pub fn is_loaded(&self) -> bool {
2014 matches!(self, Self::Loaded)
2015 }
2016
2017 pub fn is_failed(&self) -> bool {
2019 matches!(self, Self::Failed(_))
2020 }
2021}
2022
2023#[derive(Error, Debug, Clone)]
2025#[expect(
2026 missing_docs,
2027 reason = "Adding docs to the variants would not add information beyond the error message and the names"
2028)]
2029pub enum AssetLoadError {
2030 #[error("Requested handle of type {requested:?} for asset '{path}' does not match actual asset type '{actual_asset_name}', which used loader '{loader_name}'")]
2031 RequestedHandleTypeMismatch {
2032 path: AssetPath<'static>,
2033 requested: TypeId,
2034 actual_asset_name: &'static str,
2035 loader_name: &'static str,
2036 },
2037 #[error("Could not find an asset loader matching: Loader Name: {loader_name:?}; Asset Type: {asset_type_id:?}; Extension: {extension:?}; Path: {asset_path:?};")]
2038 MissingAssetLoader {
2039 loader_name: Option<String>,
2040 asset_type_id: Option<TypeId>,
2041 extension: Option<String>,
2042 asset_path: Option<String>,
2043 },
2044 #[error(transparent)]
2045 MissingAssetLoaderForExtension(#[from] MissingAssetLoaderForExtensionError),
2046 #[error(transparent)]
2047 MissingAssetLoaderForTypeName(#[from] MissingAssetLoaderForTypeNameError),
2048 #[error(transparent)]
2049 MissingAssetLoaderForTypeIdError(#[from] MissingAssetLoaderForTypeIdError),
2050 #[error(transparent)]
2051 AssetReaderError(#[from] AssetReaderError),
2052 #[error(transparent)]
2053 MissingAssetSourceError(#[from] MissingAssetSourceError),
2054 #[error(transparent)]
2055 MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError),
2056 #[error("Encountered an error while reading asset metadata bytes")]
2057 AssetMetaReadError,
2058 #[error("Failed to deserialize meta for asset {path}: {error}")]
2059 DeserializeMeta {
2060 path: AssetPath<'static>,
2061 error: Box<DeserializeMetaError>,
2062 },
2063 #[error("Asset '{path}' is configured to be processed. It cannot be loaded directly.")]
2064 #[from(ignore)]
2065 CannotLoadProcessedAsset { path: AssetPath<'static> },
2066 #[error("Asset '{path}' is configured to be ignored. It cannot be loaded.")]
2067 #[from(ignore)]
2068 CannotLoadIgnoredAsset { path: AssetPath<'static> },
2069 #[error("Failed to load asset '{path}', asset loader '{loader_name}' panicked")]
2070 AssetLoaderPanic {
2071 path: AssetPath<'static>,
2072 loader_name: &'static str,
2073 },
2074 #[error(transparent)]
2075 AssetLoaderError(#[from] AssetLoaderError),
2076 #[error(transparent)]
2077 AddAsyncError(#[from] AddAsyncError),
2078 #[error("The file at '{}' does not contain the labeled asset '{}'; it contains the following {} assets: {}",
2079 base_path,
2080 label,
2081 all_labels.len(),
2082 all_labels.iter().map(|l| format!("'{l}'")).collect::<Vec<_>>().join(", "))]
2083 MissingLabel {
2084 base_path: AssetPath<'static>,
2085 label: String,
2086 all_labels: Vec<String>,
2087 },
2088}
2089
2090#[derive(Error, Debug, Clone)]
2092#[error("Failed to load asset '{path}' with asset loader '{loader_name}': {error}")]
2093pub struct AssetLoaderError {
2094 path: AssetPath<'static>,
2095 loader_name: &'static str,
2096 error: Arc<BevyError>,
2097}
2098
2099impl AssetLoaderError {
2100 pub fn path(&self) -> &AssetPath<'static> {
2102 &self.path
2103 }
2104
2105 pub fn error(&self) -> &BevyError {
2110 &self.error
2111 }
2112}
2113
2114#[derive(Error, Debug, Clone)]
2116#[error("An error occurred while resolving an asset added by `add_async`: {error}")]
2117pub struct AddAsyncError {
2118 error: Arc<dyn core::error::Error + Send + Sync + 'static>,
2119}
2120
2121#[derive(Error, Debug, Clone, PartialEq, Eq)]
2123#[error("no `AssetLoader` found{}", format_missing_asset_ext(extensions))]
2124pub struct MissingAssetLoaderForExtensionError {
2125 extensions: Vec<String>,
2126}
2127
2128#[derive(Error, Debug, Clone, PartialEq, Eq)]
2130#[error("no `AssetLoader` found with the name '{type_name}'")]
2131pub struct MissingAssetLoaderForTypeNameError {
2132 pub type_name: String,
2134}
2135
2136#[derive(Error, Debug, Clone, PartialEq, Eq)]
2138#[error("no `AssetLoader` found with the ID '{type_id:?}'")]
2139pub struct MissingAssetLoaderForTypeIdError {
2140 pub type_id: TypeId,
2142}
2143
2144fn format_missing_asset_ext(exts: &[String]) -> String {
2145 if !exts.is_empty() {
2146 format!(
2147 " for the following extension{}: {}",
2148 if exts.len() > 1 { "s" } else { "" },
2149 exts.join(", ")
2150 )
2151 } else {
2152 " for file with no extension".to_string()
2153 }
2154}
2155
2156impl core::fmt::Debug for AssetServer {
2157 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2158 f.debug_struct("AssetServer")
2159 .field("info", &self.data.infos.read())
2160 .finish()
2161 }
2162}
2163
2164const UNTYPED_SOURCE_SUFFIX: &str = "--untyped";
2167
2168#[derive(Error, Debug, Clone)]
2170pub enum WaitForAssetError {
2171 #[error("tried to wait for an asset that is not being loaded")]
2173 NotLoaded,
2174 #[error(transparent)]
2176 Failed(Arc<AssetLoadError>),
2177 #[error(transparent)]
2179 DependencyFailed(Arc<AssetLoadError>),
2180}
2181
2182#[derive(Error, Debug)]
2183pub enum WriteDefaultMetaError {
2184 #[error(transparent)]
2185 MissingAssetLoader(#[from] MissingAssetLoaderForExtensionError),
2186 #[error(transparent)]
2187 MissingAssetSource(#[from] MissingAssetSourceError),
2188 #[error(transparent)]
2189 MissingAssetWriter(#[from] MissingAssetWriterError),
2190 #[error("failed to write default asset meta file: {0}")]
2191 FailedToWriteMeta(#[from] AssetWriterError),
2192 #[error("asset meta file already exists, so avoiding overwrite")]
2193 MetaAlreadyExists,
2194 #[error("encountered an I/O error while reading the existing meta file: {0}")]
2195 IoErrorFromExistingMetaCheck(Arc<std::io::Error>),
2196 #[error("encountered HTTP status {0} when reading the existing meta file")]
2197 HttpErrorFromExistingMetaCheck(u16),
2198}