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, VisitAssetDependencies,
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::{
37 any::{type_name, TypeId},
38 future::Future,
39 panic::AssertUnwindSafe,
40 task::Poll,
41};
42use crossbeam_channel::{Receiver, Sender};
43use futures_lite::{FutureExt, StreamExt};
44use info::*;
45use loaders::*;
46use std::path::{Path, PathBuf};
47use thiserror::Error;
48use tracing::{error, info, warn};
49
50#[derive(Resource, Clone)]
66pub struct AssetServer {
67 pub(crate) data: Arc<AssetServerData>,
68}
69
70pub(crate) struct AssetServerData {
72 pub(crate) infos: RwLock<AssetInfos>,
73 pub(crate) loaders: Arc<RwLock<AssetLoaders>>,
74 asset_event_sender: Sender<InternalAssetEvent>,
75 asset_event_receiver: Receiver<InternalAssetEvent>,
76 sources: Arc<AssetSources>,
77 mode: AssetServerMode,
78 meta_check: AssetMetaCheck,
79 unapproved_path_mode: UnapprovedPathMode,
80}
81
82#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub enum AssetServerMode {
85 Unprocessed,
87 Processed,
89}
90
91impl AssetServer {
92 pub const STARTED_LOAD_COUNT: DiagnosticPath = DiagnosticPath::const_new("started_load_count");
94
95 pub fn new(
98 sources: Arc<AssetSources>,
99 mode: AssetServerMode,
100 watching_for_changes: bool,
101 unapproved_path_mode: UnapprovedPathMode,
102 ) -> Self {
103 Self::new_with_loaders(
104 sources,
105 Default::default(),
106 mode,
107 AssetMetaCheck::Always,
108 watching_for_changes,
109 unapproved_path_mode,
110 )
111 }
112
113 pub fn new_with_meta_check(
116 sources: Arc<AssetSources>,
117 mode: AssetServerMode,
118 meta_check: AssetMetaCheck,
119 watching_for_changes: bool,
120 unapproved_path_mode: UnapprovedPathMode,
121 ) -> Self {
122 Self::new_with_loaders(
123 sources,
124 Default::default(),
125 mode,
126 meta_check,
127 watching_for_changes,
128 unapproved_path_mode,
129 )
130 }
131
132 pub(crate) fn new_with_loaders(
133 sources: Arc<AssetSources>,
134 loaders: Arc<RwLock<AssetLoaders>>,
135 mode: AssetServerMode,
136 meta_check: AssetMetaCheck,
137 watching_for_changes: bool,
138 unapproved_path_mode: UnapprovedPathMode,
139 ) -> Self {
140 let (asset_event_sender, asset_event_receiver) = crossbeam_channel::unbounded();
141 let mut infos = AssetInfos::default();
142 infos.watching_for_changes = watching_for_changes;
143 Self {
144 data: Arc::new(AssetServerData {
145 sources,
146 mode,
147 meta_check,
148 asset_event_sender,
149 asset_event_receiver,
150 loaders,
151 infos: RwLock::new(infos),
152 unapproved_path_mode,
153 }),
154 }
155 }
156
157 pub(crate) fn read_infos(&self) -> RwLockReadGuard<'_, AssetInfos> {
158 self.data
159 .infos
160 .read()
161 .unwrap_or_else(PoisonError::into_inner)
162 }
163
164 pub(crate) fn write_infos(&self) -> RwLockWriteGuard<'_, AssetInfos> {
165 self.data
166 .infos
167 .write()
168 .unwrap_or_else(PoisonError::into_inner)
169 }
170
171 fn read_loaders(&self) -> RwLockReadGuard<'_, AssetLoaders> {
172 self.data
173 .loaders
174 .read()
175 .unwrap_or_else(PoisonError::into_inner)
176 }
177
178 fn write_loaders(&self) -> RwLockWriteGuard<'_, AssetLoaders> {
179 self.data
180 .loaders
181 .write()
182 .unwrap_or_else(PoisonError::into_inner)
183 }
184
185 pub fn get_source<'a>(
187 &self,
188 source: impl Into<AssetSourceId<'a>>,
189 ) -> Result<&AssetSource, MissingAssetSourceError> {
190 self.data.sources.get(source.into())
191 }
192
193 pub fn watching_for_changes(&self) -> bool {
195 self.read_infos().watching_for_changes
196 }
197
198 pub fn register_loader<L: AssetLoader>(&self, loader: L) {
200 self.write_loaders().push(loader);
201 }
202
203 pub fn register_asset<A: Asset>(&self, assets: &Assets<A>) {
205 self.register_handle_provider(assets.get_handle_provider());
206 fn sender<A: Asset>(world: &mut World, index: AssetIndex) {
207 world
208 .resource_mut::<Messages<AssetEvent<A>>>()
209 .write(AssetEvent::LoadedWithDependencies { id: index.into() });
210 }
211 fn failed_sender<A: Asset>(
212 world: &mut World,
213 index: AssetIndex,
214 path: AssetPath<'static>,
215 error: AssetLoadError,
216 ) {
217 world
218 .resource_mut::<Messages<AssetLoadFailedEvent<A>>>()
219 .write(AssetLoadFailedEvent {
220 id: index.into(),
221 path,
222 error,
223 });
224 }
225
226 let mut infos = self.write_infos();
227
228 infos
229 .dependency_loaded_event_sender
230 .insert(TypeId::of::<A>(), sender::<A>);
231
232 infos
233 .dependency_failed_event_sender
234 .insert(TypeId::of::<A>(), failed_sender::<A>);
235 }
236
237 pub(crate) fn register_handle_provider(&self, handle_provider: AssetHandleProvider) {
238 self.write_infos()
239 .handle_providers
240 .insert(handle_provider.type_id, handle_provider);
241 }
242
243 pub async fn get_asset_loader_with_extension(
245 &self,
246 extension: &str,
247 ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForExtensionError> {
248 let error = || MissingAssetLoaderForExtensionError {
249 extensions: vec![extension.to_string()],
250 };
251
252 let loader = self
253 .read_loaders()
254 .get_by_extension(extension)
255 .ok_or_else(error)?;
256 loader.get().await.map_err(|_| error())
257 }
258
259 pub async fn get_asset_loader_with_type_name(
261 &self,
262 type_name: &str,
263 ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForTypeNameError> {
264 let error = || MissingAssetLoaderForTypeNameError {
265 type_name: type_name.to_string(),
266 };
267
268 let loader = self
269 .read_loaders()
270 .get_by_name(type_name)
271 .ok_or_else(error)?;
272 loader.get().await.map_err(|_| error())
273 }
274
275 pub async fn get_path_asset_loader<'a>(
277 &self,
278 path: impl Into<AssetPath<'a>>,
279 ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForExtensionError> {
280 let path = path.into();
281
282 let error = || {
283 let Some(full_extension) = path.get_full_extension() else {
284 return MissingAssetLoaderForExtensionError {
285 extensions: Vec::new(),
286 };
287 };
288
289 let mut extensions = vec![full_extension.to_string()];
290 extensions.extend(
291 AssetPath::iter_secondary_extensions(full_extension).map(ToString::to_string),
292 );
293
294 MissingAssetLoaderForExtensionError { extensions }
295 };
296
297 let loader = self.read_loaders().get_by_path(&path).ok_or_else(error)?;
298 loader.get().await.map_err(|_| error())
299 }
300
301 pub async fn get_asset_loader_with_asset_type_id(
303 &self,
304 type_id: TypeId,
305 ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForTypeIdError> {
306 let error = || MissingAssetLoaderForTypeIdError { type_id };
307
308 let loader = self.read_loaders().get_by_type(type_id).ok_or_else(error)?;
309 loader.get().await.map_err(|_| error())
310 }
311
312 pub async fn get_asset_loader_with_asset_type<A: Asset>(
314 &self,
315 ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForTypeIdError> {
316 self.get_asset_loader_with_asset_type_id(TypeId::of::<A>())
317 .await
318 }
319
320 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
364 pub fn load<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Handle<A> {
365 self.load_builder().load(path.into())
366 }
367
368 #[must_use = "the load doesn't start until LoadBuilder has been consumed"]
371 pub fn load_builder(&self) -> LoadBuilder<'_> {
372 LoadBuilder::new(self)
373 }
374
375 pub(crate) fn load_with_meta_transform<'a, G: Send + Sync + 'static>(
376 &self,
377 path: impl Into<AssetPath<'a>>,
378 type_id: TypeId,
379 type_name: Option<&str>,
380 meta_transform: Option<MetaTransform>,
381 guard: G,
382 override_unapproved: bool,
383 ) -> UntypedHandle {
384 let path = path.into().into_owned();
385 if let Err(err) = validate_path(&path, override_unapproved, self.data.unapproved_path_mode)
386 {
387 error!("{err}");
389 return UntypedHandle::default_for_type(type_id);
390 }
391
392 let mut infos = self.write_infos();
393 let (handle, should_load) = infos.get_or_create_path_handle_erased(
394 path.clone(),
395 type_id,
396 type_name,
397 HandleLoadingMode::Request,
398 meta_transform,
399 );
400
401 if should_load {
402 self.spawn_load_task(handle.clone(), path, infos, guard);
403 }
404
405 handle
406 }
407
408 pub(crate) fn spawn_load_task<G: Send + Sync + 'static>(
409 &self,
410 handle: UntypedHandle,
411 path: AssetPath<'static>,
412 mut infos: RwLockWriteGuard<AssetInfos>,
413 guard: G,
414 ) {
415 infos.stats.started_load_tasks += 1;
416
417 bevy_tasks::cfg::multi_threaded! {
418 if {} else {
419 drop(infos);
422 }
423 }
424
425 let owned_handle = handle.clone();
426 let server = self.clone();
427 let task = IoTaskPool::get().spawn(async move {
428 if let Err(err) = server
429 .load_internal(Some(owned_handle), path, false, None)
430 .await
431 {
432 error!("{}", err);
433 }
434 drop(guard);
435 });
436
437 let mut infos = bevy_tasks::cfg::multi_threaded! {
438 if {
439 infos
440 } else {
441 self.write_infos()
443 }
444 };
445 infos
446 .pending_tasks
447 .insert((&handle).try_into().unwrap(), task);
448 }
449
450 pub(crate) fn load_unknown_type_with_meta_transform<'a, G: Send + Sync + 'static>(
451 &self,
452 path: impl Into<AssetPath<'a>>,
453 meta_transform: Option<MetaTransform>,
454 guard: G,
455 override_unapproved: bool,
456 ) -> Handle<LoadedUntypedAsset> {
457 let path = path.into().into_owned();
458 if let Err(err) = validate_path(&path, override_unapproved, self.data.unapproved_path_mode)
459 {
460 error!("{err}");
462 return Handle::default();
463 }
464
465 let untyped_source = AssetSourceId::Name(match path.source() {
466 AssetSourceId::Default => CowArc::Static(UNTYPED_SOURCE_SUFFIX),
467 AssetSourceId::Name(source) => {
468 CowArc::Owned(format!("{source}--{UNTYPED_SOURCE_SUFFIX}").into())
469 }
470 });
471 let mut infos = self.write_infos();
472 let (handle, should_load) = infos.get_or_create_path_handle::<LoadedUntypedAsset>(
473 path.clone().with_source(untyped_source),
474 HandleLoadingMode::Request,
475 meta_transform,
476 );
477
478 if !should_load {
479 return handle;
480 }
481 let index = (&handle).try_into().unwrap();
482
483 infos.stats.started_load_tasks += 1;
484
485 bevy_tasks::cfg::multi_threaded! {
486 if {} else {
487 drop(infos);
490 }
491 }
492
493 let server = self.clone();
494 let task = IoTaskPool::get().spawn(async move {
495 let path_clone = path.clone();
496 match server
497 .load_internal(None, path, false, None)
498 .await
499 .map(|h| {
500 h.expect("handle must be returned, since we didn't pass in an input handle")
501 }) {
502 Ok(handle) => server.send_asset_event(InternalAssetEvent::Loaded {
503 index,
504 loaded_asset: LoadedAsset::new_with_dependencies(LoadedUntypedAsset { handle })
505 .into(),
506 }),
507 Err(err) => {
508 error!("{err}");
509 server.send_asset_event(InternalAssetEvent::Failed {
510 index,
511 path: path_clone,
512 error: err,
513 });
514 }
515 };
516 drop(guard);
517 });
518
519 let mut infos = bevy_tasks::cfg::multi_threaded! {
520 if {
521 infos
522 } else {
523 self.write_infos()
525 }
526 };
527 infos.pending_tasks.insert(index, task);
528
529 handle
530 }
531
532 async fn load_internal<'a>(
541 &self,
542 input_handle: Option<UntypedHandle>,
543 path: AssetPath<'a>,
544 force: bool,
545 meta_transform: Option<MetaTransform>,
546 ) -> Result<Option<UntypedHandle>, AssetLoadError> {
547 let input_handle_type_id = input_handle.as_ref().map(UntypedHandle::type_id);
548
549 let path = path.into_owned();
550 let path_clone = path.clone();
551 let (mut meta, loader, mut reader) = self
552 .get_meta_loader_and_reader(&path_clone, input_handle_type_id)
553 .await
554 .inspect_err(|e| {
555 if let Some(handle) = &input_handle {
558 self.send_asset_event(InternalAssetEvent::Failed {
559 index: handle.try_into().unwrap(),
560 path: path.clone_owned(),
561 error: e.clone(),
562 });
563 }
564 })?;
565
566 assert!(input_handle.is_none() || meta_transform.is_none());
571 if let Some(input_handle) = input_handle.as_ref() {
572 let index = match input_handle.id() {
573 UntypedAssetId::Uuid { .. } => unreachable!("we never load a UUID handle"),
574 UntypedAssetId::Index { type_id, index } => ErasedAssetIndex::new(index, type_id),
575 };
576 let infos = self.read_infos();
578 let info = infos.get(index).unwrap();
581 if let Some(meta_transform) = info.meta_transform.as_ref() {
582 (*meta_transform)(&mut *meta);
583 }
584 }
585 if let Some(meta_transform) = meta_transform.as_ref() {
586 (*meta_transform)(&mut *meta);
587 }
588
589 let asset_id: Option<ErasedAssetIndex>; let fetched_handle; let should_load; if let Some(input_handle) = input_handle {
593 asset_id = Some((&input_handle).try_into().unwrap());
596 fetched_handle = None;
599 should_load = true;
601 } else {
602 let mut infos = self.write_infos();
608 let result = infos.get_or_create_path_handle_internal(
609 path.clone(),
610 path.label().is_none().then(|| loader.asset_type_id()),
611 HandleLoadingMode::Request,
612 meta_transform,
613 );
614 match unwrap_with_context(
615 result,
616 loader.asset_type_id(),
617 Some(loader.asset_type_name()),
618 ) {
619 None => {
622 asset_id = None;
625 fetched_handle = None;
626 should_load = true;
629 }
630 Some((handle, result_should_load)) => {
631 asset_id = Some((&handle).try_into().unwrap());
634 fetched_handle = Some(handle);
635 should_load = result_should_load;
636 }
637 }
638 }
639 if let Some(asset_type_id) = asset_id.map(|id| id.type_id) {
641 if path.label().is_none() && asset_type_id != loader.asset_type_id() {
644 error!(
645 "Expected {:?}, got {:?}",
646 asset_type_id,
647 loader.asset_type_id()
648 );
649 return Err(Box::new(RequestedHandleTypeMismatchError {
650 path: path.into_owned(),
651 requested: asset_type_id,
652 actual_asset_name: loader.asset_type_name(),
653 loader_name: loader.type_path(),
654 })
655 .into());
656 }
657 }
658 if !should_load && !force {
660 return Ok(fetched_handle);
661 }
662
663 let (base_asset_id, _base_handle, base_path) = if path.label().is_some() {
667 let mut infos = self.write_infos();
668 let base_path = path.without_label().into_owned();
669 let base_handle = infos
670 .get_or_create_path_handle_erased(
671 base_path.clone(),
672 loader.asset_type_id(),
673 Some(loader.asset_type_name()),
674 HandleLoadingMode::Force,
675 None,
676 )
677 .0;
678 (
679 (&base_handle).try_into().unwrap(),
682 Some(base_handle),
683 base_path,
684 )
685 } else {
686 (asset_id.unwrap(), None, path.clone())
687 };
688
689 match self
690 .load_with_settings_loader_and_reader(
691 &base_path,
692 meta.loader_settings().expect("meta is set to Load"),
693 &*loader,
694 &mut *reader,
695 true,
696 false,
697 )
698 .await
699 {
700 Ok(loaded_asset) => {
701 let final_handle = if let Some(label) = path.label_cow() {
702 match loaded_asset.label_to_asset_index.get(&label) {
703 Some(labeled_asset) => {
704 let labeled_asset = &loaded_asset.labeled_assets[*labeled_asset];
705 if let Some(asset_id) = asset_id
708 && asset_id.type_id != labeled_asset.handle.type_id()
709 {
710 let error: AssetLoadError =
711 Box::new(RequestedHandleTypeMismatchError {
712 path: path.clone(),
713 requested: asset_id.type_id,
714 actual_asset_name: labeled_asset
715 .asset
716 .value
717 .asset_type_name(),
718 loader_name: loader.type_path(),
719 })
720 .into();
721 self.send_asset_event(InternalAssetEvent::Failed {
722 index: asset_id,
723 error: error.clone(),
724 path: path.into_owned(),
725 });
726 return Err(error);
727 }
728 Some(labeled_asset.handle.clone())
729 }
730 None => {
731 let mut all_labels: Vec<String> = loaded_asset
732 .label_to_asset_index
733 .keys()
734 .map(|s| (**s).to_owned())
735 .collect();
736 all_labels.sort_unstable();
737 let error = AssetLoadError::MissingLabel {
738 base_path,
739 label: label.to_string(),
740 all_labels,
741 };
742 if let Some(asset_id) = asset_id {
743 self.send_asset_event(InternalAssetEvent::Failed {
744 index: asset_id,
745 error: error.clone(),
746 path: path.into_owned(),
747 });
748 }
749 return Err(error);
750 }
751 }
752 } else {
753 fetched_handle
754 };
755
756 self.send_asset_event(InternalAssetEvent::Loaded {
757 index: base_asset_id,
758 loaded_asset,
759 });
760 Ok(final_handle)
761 }
762 Err(err) => {
763 if let Some(asset_id) = asset_id {
764 self.send_asset_event(InternalAssetEvent::Failed {
765 index: asset_id,
766 error: err.clone(),
767 path: path.into_owned(),
768 });
769 }
770 Err(err)
771 }
772 }
773 }
774
775 pub fn reload<'a>(&self, path: impl Into<AssetPath<'a>>) {
777 self.reload_internal(path, false);
778 }
779
780 fn reload_internal<'a>(&self, path: impl Into<AssetPath<'a>>, log: bool) {
781 let server = self.clone();
782 let path = path.into().into_owned();
783 IoTaskPool::get()
784 .spawn(async move {
785 let mut reloaded = false;
786
787 let requests = server
790 .read_infos()
791 .get_path_handles(&path)
792 .map(|handle| server.load_internal(Some(handle), path.clone(), true, None))
793 .collect::<Vec<_>>();
794
795 for result in requests {
796 server.write_infos().stats.started_load_tasks += 1;
798 match result.await {
799 Ok(_) => reloaded = true,
800 Err(err) => error!("{}", err),
801 }
802 }
803
804 if !reloaded && server.read_infos().should_reload(&path) {
813 server.write_infos().stats.started_load_tasks += 1;
814 match server.load_internal(None, path.clone(), true, None).await {
815 Ok(_) => reloaded = true,
816 Err(err) => error!("{}", err),
817 }
818 }
819
820 if log && reloaded {
821 info!("Reloaded {}", path);
822 }
823 })
824 .detach();
825 }
826
827 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
832 pub fn add<A: Asset>(&self, asset: A) -> Handle<A> {
833 self.load_asset(LoadedAsset::new_with_dependencies(asset))
834 }
835
836 pub(crate) fn load_asset<A: Asset>(&self, asset: impl Into<LoadedAsset<A>>) -> Handle<A> {
837 let loaded_asset: LoadedAsset<A> = asset.into();
838 let erased_loaded_asset: ErasedLoadedAsset = loaded_asset.into();
839 self.load_asset_untyped(None, erased_loaded_asset)
840 .typed_debug_checked()
841 }
842
843 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
844 pub(crate) fn load_asset_untyped(
845 &self,
846 path: Option<AssetPath<'static>>,
847 asset: impl Into<ErasedLoadedAsset>,
848 ) -> UntypedHandle {
849 let loaded_asset = asset.into();
850 let handle = if let Some(path) = path {
851 let (handle, _) = self.write_infos().get_or_create_path_handle_erased(
852 path,
853 loaded_asset.asset_type_id(),
854 Some(loaded_asset.asset_type_name()),
855 HandleLoadingMode::NotLoading,
856 None,
857 );
858 handle
859 } else {
860 self.write_infos().create_loading_handle_untyped(
861 loaded_asset.asset_type_id(),
862 loaded_asset.asset_type_name(),
863 )
864 };
865 self.send_asset_event(InternalAssetEvent::Loaded {
866 index: (&handle).try_into().unwrap(),
868 loaded_asset,
869 });
870 handle
871 }
872
873 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
878 pub fn add_async<A: Asset, E: core::error::Error + Send + Sync + 'static>(
879 &self,
880 future: impl Future<Output = Result<A, E>> + Send + 'static,
881 ) -> Handle<A> {
882 let mut infos = self.write_infos();
883 let handle = infos.create_loading_handle_untyped(TypeId::of::<A>(), type_name::<A>());
884
885 bevy_tasks::cfg::multi_threaded! {
886 if {} else {
887 drop(infos);
890 }
891 }
892
893 let index = (&handle).try_into().unwrap();
895
896 let event_sender = self.data.asset_event_sender.clone();
897
898 let task = IoTaskPool::get().spawn(async move {
899 match future.await {
900 Ok(asset) => {
901 let loaded_asset = LoadedAsset::new_with_dependencies(asset).into();
902 event_sender
903 .send(InternalAssetEvent::Loaded {
904 index,
905 loaded_asset,
906 })
907 .unwrap();
908 }
909 Err(error) => {
910 let error = AddAsyncError {
911 error: Arc::new(error),
912 };
913 error!("{error}");
914 event_sender
915 .send(InternalAssetEvent::Failed {
916 index,
917 path: Default::default(),
918 error: AssetLoadError::AddAsyncError(error),
919 })
920 .unwrap();
921 }
922 }
923 });
924
925 let mut infos = bevy_tasks::cfg::multi_threaded! {
926 if {
927 infos
928 } else {
929 self.write_infos()
931 }
932 };
933 infos.pending_tasks.insert(index, task);
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.write_infos().get_or_create_path_handle(
950 path.clone(),
951 HandleLoadingMode::Request,
952 None,
953 );
954 if !should_load {
955 return handle;
956 }
957 let index = (&handle).try_into().unwrap();
959 self.write_infos().stats.started_load_tasks += 1;
960 self.load_folder_internal(index, path);
961
962 handle
963 }
964
965 pub(crate) fn load_folder_internal(&self, index: ErasedAssetIndex, path: AssetPath) {
966 async fn load_folder<'a>(
967 source: AssetSourceId<'static>,
968 path: &'a Path,
969 reader: &'a dyn ErasedAssetReader,
970 server: &'a AssetServer,
971 handles: &'a mut Vec<UntypedHandle>,
972 ) -> Result<(), AssetLoadError> {
973 let is_dir = reader.is_directory(path).await?;
974 if is_dir {
975 let mut path_stream = reader.read_directory(path.as_ref()).await?;
976 while let Some(child_path) = path_stream.next().await {
977 if reader.is_directory(&child_path).await? {
978 Box::pin(load_folder(
979 source.clone(),
980 &child_path,
981 reader,
982 server,
983 handles,
984 ))
985 .await?;
986 } else {
987 let path = child_path.to_str().expect("Path should be a valid string.");
988 let asset_path = AssetPath::parse(path).with_source(source.clone());
989 match server.load_builder().load_untyped_async(asset_path).await {
990 Ok(handle) => handles.push(handle),
991 Err(
993 AssetLoadError::MissingAssetLoaderForTypeName(_)
994 | AssetLoadError::MissingAssetLoaderForExtension(_),
995 ) => {}
996 Err(err) => return Err(err),
997 }
998 }
999 }
1000 }
1001 Ok(())
1002 }
1003
1004 let path = path.into_owned();
1005 let server = self.clone();
1006 IoTaskPool::get()
1007 .spawn(async move {
1008 let Ok(source) = server.get_source(path.source()) else {
1009 error!(
1010 "Failed to load {path}. AssetSource {} does not exist",
1011 path.source()
1012 );
1013 return;
1014 };
1015
1016 let asset_reader = match server.data.mode {
1017 AssetServerMode::Unprocessed => source.reader(),
1018 AssetServerMode::Processed => match source.processed_reader() {
1019 Ok(reader) => reader,
1020 Err(_) => {
1021 error!(
1022 "Failed to load {path}. AssetSource {} does not have a processed AssetReader",
1023 path.source()
1024 );
1025 return;
1026 }
1027 },
1028 };
1029
1030 let mut handles = Vec::new();
1031 match load_folder(source.id(), path.path(), asset_reader, &server, &mut handles).await {
1032 Ok(_) => server.send_asset_event(InternalAssetEvent::Loaded {
1033 index,
1034 loaded_asset: LoadedAsset::new_with_dependencies(
1035 LoadedFolder { handles },
1036 )
1037 .into(),
1038 }),
1039 Err(err) => {
1040 error!("Failed to load folder. {err}");
1041 server.send_asset_event(InternalAssetEvent::Failed { index, error: err, path });
1042 },
1043 }
1044 })
1045 .detach();
1046 }
1047
1048 fn send_asset_event(&self, event: InternalAssetEvent) {
1049 self.data.asset_event_sender.send(event).unwrap();
1050 }
1051
1052 pub fn get_load_states(
1054 &self,
1055 id: impl Into<UntypedAssetId>,
1056 ) -> Option<(LoadState, DependencyLoadState, RecursiveDependencyLoadState)> {
1057 let Ok(index) = id.into().try_into() else {
1058 return None;
1060 };
1061 self.read_infos().get(index).map(|i| {
1062 (
1063 i.load_state.clone(),
1064 i.dep_load_state.clone(),
1065 i.rec_dep_load_state.clone(),
1066 )
1067 })
1068 }
1069
1070 pub fn get_load_state(&self, id: impl Into<UntypedAssetId>) -> Option<LoadState> {
1076 let Ok(index) = id.into().try_into() else {
1077 return None;
1079 };
1080 self.read_infos().get(index).map(|i| i.load_state.clone())
1081 }
1082
1083 pub fn get_dependency_load_state(
1089 &self,
1090 id: impl Into<UntypedAssetId>,
1091 ) -> Option<DependencyLoadState> {
1092 let Ok(index) = id.into().try_into() else {
1093 return None;
1095 };
1096 self.read_infos()
1097 .get(index)
1098 .map(|i| i.dep_load_state.clone())
1099 }
1100
1101 pub fn get_recursive_dependency_load_state(
1107 &self,
1108 id: impl Into<UntypedAssetId>,
1109 ) -> Option<RecursiveDependencyLoadState> {
1110 let Ok(index) = id.into().try_into() else {
1111 return None;
1113 };
1114 self.read_infos()
1115 .get(index)
1116 .map(|i| i.rec_dep_load_state.clone())
1117 }
1118
1119 pub fn load_state(&self, id: impl Into<UntypedAssetId>) -> LoadState {
1124 self.get_load_state(id).unwrap_or(LoadState::NotLoaded)
1125 }
1126
1127 pub fn dependency_load_state(&self, id: impl Into<UntypedAssetId>) -> DependencyLoadState {
1132 self.get_dependency_load_state(id)
1133 .unwrap_or(DependencyLoadState::NotLoaded)
1134 }
1135
1136 pub fn recursive_dependency_load_state(
1141 &self,
1142 id: impl Into<UntypedAssetId>,
1143 ) -> RecursiveDependencyLoadState {
1144 self.get_recursive_dependency_load_state(id)
1145 .unwrap_or(RecursiveDependencyLoadState::NotLoaded)
1146 }
1147
1148 pub fn is_loaded(&self, id: impl Into<UntypedAssetId>) -> bool {
1150 matches!(self.load_state(id), LoadState::Loaded)
1151 }
1152
1153 pub fn is_loaded_with_direct_dependencies(&self, id: impl Into<UntypedAssetId>) -> bool {
1155 matches!(
1156 self.get_load_states(id),
1157 Some((LoadState::Loaded, DependencyLoadState::Loaded, _))
1158 )
1159 }
1160
1161 pub fn is_loaded_with_dependencies(&self, id: impl Into<UntypedAssetId>) -> bool {
1164 matches!(
1165 self.get_load_states(id),
1166 Some((
1167 LoadState::Loaded,
1168 DependencyLoadState::Loaded,
1169 RecursiveDependencyLoadState::Loaded
1170 ))
1171 )
1172 }
1173
1174 pub fn are_dependencies_loaded(&self, value: &impl VisitAssetDependencies) -> bool {
1178 let infos = self.read_infos();
1179 let mut loaded = true;
1180 value.visit_dependencies(&mut |asset_id| {
1181 let index = match asset_id {
1182 UntypedAssetId::Uuid { .. } => return,
1184 UntypedAssetId::Index { type_id, index } => ErasedAssetIndex::new(index, type_id),
1185 };
1186 let Some(info) = infos.get(index) else {
1187 loaded = false;
1189 return;
1190 };
1191
1192 if !info.rec_dep_load_state.is_loaded() {
1193 loaded = false;
1194 }
1195 });
1196 loaded
1197 }
1198
1199 pub fn are_direct_dependencies_loaded(&self, value: &impl VisitAssetDependencies) -> bool {
1203 let infos = self.read_infos();
1204 let mut loaded = true;
1205 value.visit_dependencies(&mut |asset_id| {
1206 let index = match asset_id {
1207 UntypedAssetId::Uuid { .. } => return,
1209 UntypedAssetId::Index { type_id, index } => ErasedAssetIndex::new(index, type_id),
1210 };
1211 let Some(info) = infos.get(index) else {
1212 loaded = false;
1214 return;
1215 };
1216
1217 if !info.dep_load_state.is_loaded() {
1218 loaded = false;
1219 }
1220 });
1221 loaded
1222 }
1223
1224 pub fn get_handle<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Option<Handle<A>> {
1227 self.get_path_and_type_id_handle(&path.into(), TypeId::of::<A>())
1228 .map(UntypedHandle::typed_debug_checked)
1229 }
1230
1231 pub fn get_id_handle<A: Asset>(&self, id: AssetId<A>) -> Option<Handle<A>> {
1239 self.get_id_handle_untyped(id.untyped())
1240 .map(UntypedHandle::typed)
1241 }
1242
1243 pub fn get_id_handle_untyped(&self, id: UntypedAssetId) -> Option<UntypedHandle> {
1246 let Ok(index) = id.try_into() else {
1247 return None;
1249 };
1250 self.read_infos().get_index_handle(index)
1251 }
1252
1253 pub fn is_managed(&self, id: impl Into<UntypedAssetId>) -> bool {
1256 let Ok(index) = id.into().try_into() else {
1257 return false;
1259 };
1260 self.read_infos().contains_key(index)
1261 }
1262
1263 pub fn get_path_id<'a>(&self, path: impl Into<AssetPath<'a>>) -> Option<UntypedAssetId> {
1270 let infos = self.read_infos();
1271 let path = path.into();
1272 let mut ids = infos.get_path_indices(&path);
1273 ids.next().map(Into::into)
1274 }
1275
1276 pub fn get_path_ids<'a>(&self, path: impl Into<AssetPath<'a>>) -> Vec<UntypedAssetId> {
1280 let path = path.into();
1281 self.read_infos()
1282 .get_path_indices(&path)
1283 .map(Into::into)
1284 .collect()
1285 }
1286
1287 pub fn get_handle_untyped<'a>(&self, path: impl Into<AssetPath<'a>>) -> Option<UntypedHandle> {
1294 let path = path.into();
1295 self.read_infos().get_path_handles(&path).next()
1296 }
1297
1298 pub fn get_handles_untyped<'a>(&self, path: impl Into<AssetPath<'a>>) -> Vec<UntypedHandle> {
1302 let path = path.into();
1303 self.read_infos().get_path_handles(&path).collect()
1304 }
1305
1306 pub fn get_path_and_type_id_handle(
1309 &self,
1310 path: &AssetPath,
1311 type_id: TypeId,
1312 ) -> Option<UntypedHandle> {
1313 let path = path.into();
1314 self.read_infos()
1315 .get_path_and_type_id_handle(&path, type_id)
1316 }
1317
1318 pub fn get_path(&self, id: impl Into<UntypedAssetId>) -> Option<AssetPath<'_>> {
1320 let Ok(index) = id.into().try_into() else {
1321 return None;
1323 };
1324 let infos = self.read_infos();
1325 let info = infos.get(index)?;
1326 Some(info.path.as_ref()?.clone())
1327 }
1328
1329 pub fn mode(&self) -> AssetServerMode {
1331 self.data.mode
1332 }
1333
1334 pub fn preregister_loader<L: AssetLoader>(&self, extensions: &[&str]) {
1339 self.write_loaders().reserve::<L>(extensions);
1340 }
1341
1342 pub(crate) fn get_or_create_path_handle<'a, A: Asset>(
1344 &self,
1345 path: impl Into<AssetPath<'a>>,
1346 meta_transform: Option<MetaTransform>,
1347 ) -> Handle<A> {
1348 self.get_or_create_path_handle_erased(
1349 path.into().into_owned(),
1350 TypeId::of::<A>(),
1351 Some(type_name::<A>()),
1352 meta_transform,
1353 )
1354 .typed_unchecked()
1355 }
1356
1357 pub(crate) fn get_or_create_path_handle_erased<'a>(
1362 &self,
1363 path: impl Into<AssetPath<'a>>,
1364 type_id: TypeId,
1365 type_name: Option<&str>,
1366 meta_transform: Option<MetaTransform>,
1367 ) -> UntypedHandle {
1368 self.write_infos()
1369 .get_or_create_path_handle_erased(
1370 path.into().into_owned(),
1371 type_id,
1372 type_name,
1373 HandleLoadingMode::NotLoading,
1374 meta_transform,
1375 )
1376 .0
1377 }
1378
1379 pub(crate) async fn get_meta_loader_and_reader<'a>(
1380 &'a self,
1381 asset_path: &'a AssetPath<'_>,
1382 asset_type_id: Option<TypeId>,
1383 ) -> Result<
1384 (
1385 Box<dyn AssetMetaDyn>,
1386 Arc<dyn ErasedAssetLoader>,
1387 Box<dyn Reader + 'a>,
1388 ),
1389 AssetLoadError,
1390 > {
1391 let source = self.get_source(asset_path.source())?;
1392 let asset_reader = match self.data.mode {
1393 AssetServerMode::Unprocessed => source.reader(),
1394 AssetServerMode::Processed => source.processed_reader()?,
1395 };
1396 let read_meta = match &self.data.meta_check {
1397 AssetMetaCheck::Always => true,
1398 AssetMetaCheck::Paths(paths) => paths.contains(asset_path),
1399 AssetMetaCheck::Never => false,
1400 };
1401
1402 let mut meta_reader;
1407
1408 let (meta, loader) = if read_meta {
1409 match asset_reader.read_meta(asset_path.path()).await {
1410 Ok(new_meta_reader) => {
1411 meta_reader = new_meta_reader;
1412 let mut meta_bytes = vec![];
1413 meta_reader
1414 .read_to_end(&mut meta_bytes)
1415 .await
1416 .map_err(|err| AssetLoadError::AssetReaderError(err.into()))?;
1417 let minimal: AssetMetaMinimal =
1419 ron::de::from_bytes(&meta_bytes).map_err(|e| {
1420 AssetLoadError::DeserializeMeta {
1421 path: asset_path.clone_owned(),
1422 error: DeserializeMetaError::DeserializeMinimal(e).into(),
1423 }
1424 })?;
1425 let loader_name = match minimal.asset {
1426 AssetActionMinimal::Load { loader } => loader,
1427 AssetActionMinimal::Process { .. } => {
1428 return Err(AssetLoadError::CannotLoadProcessedAsset {
1429 path: asset_path.clone_owned(),
1430 })
1431 }
1432 AssetActionMinimal::Ignore => {
1433 return Err(AssetLoadError::CannotLoadIgnoredAsset {
1434 path: asset_path.clone_owned(),
1435 })
1436 }
1437 };
1438 let loader = self.get_asset_loader_with_type_name(&loader_name).await?;
1439 let meta = loader.deserialize_meta(&meta_bytes).map_err(|e| {
1440 AssetLoadError::DeserializeMeta {
1441 path: asset_path.clone_owned(),
1442 error: e.into(),
1443 }
1444 })?;
1445
1446 (meta, loader)
1447 }
1448 Err(AssetReaderError::NotFound(_)) => {
1449 let loader = { self.read_loaders().find(asset_type_id, asset_path) };
1451
1452 let error = || AssetLoadError::MissingAssetLoader {
1453 asset_type_id,
1454 asset_path: asset_path.to_string(),
1455 };
1456
1457 let loader = loader.ok_or_else(error)?.get().await.map_err(|_| error())?;
1458
1459 let meta = loader.default_meta();
1460 (meta, loader)
1461 }
1462 Err(err) => return Err(err.into()),
1463 }
1464 } else {
1465 let loader = { self.read_loaders().find(asset_type_id, asset_path) };
1466
1467 let error = || AssetLoadError::MissingAssetLoader {
1468 asset_type_id,
1469 asset_path: asset_path.to_string(),
1470 };
1471
1472 let loader = loader.ok_or_else(error)?.get().await.map_err(|_| error())?;
1473
1474 let meta = loader.default_meta();
1475 (meta, loader)
1476 };
1477 let reader = asset_reader.read(asset_path.path()).await?;
1478 Ok((meta, loader, reader))
1479 }
1480
1481 pub(crate) async fn load_with_settings_loader_and_reader(
1482 &self,
1483 asset_path: &AssetPath<'_>,
1484 settings: &dyn Settings,
1485 loader: &dyn ErasedAssetLoader,
1486 reader: &mut dyn Reader,
1487 load_dependencies: bool,
1488 populate_hashes: bool,
1489 ) -> Result<ErasedLoadedAsset, AssetLoadError> {
1490 let asset_path = asset_path.clone_owned();
1492 let load_context =
1493 LoadContext::new(self, asset_path.clone(), load_dependencies, populate_hashes);
1494 let load = AssertUnwindSafe(loader.load(reader, settings, load_context)).catch_unwind();
1495 #[cfg(feature = "trace")]
1496 let load = {
1497 use tracing::Instrument;
1498
1499 let span = tracing::info_span!(
1500 "asset loading",
1501 loader = loader.type_path(),
1502 asset = asset_path.to_string()
1503 );
1504 load.instrument(span)
1505 };
1506 load.await
1507 .map_err(|_| AssetLoadError::AssetLoaderPanic {
1508 path: asset_path.clone_owned(),
1509 loader_name: loader.type_path(),
1510 })?
1511 .map_err(|e| {
1512 AssetLoadError::AssetLoaderError(AssetLoaderError {
1513 path: asset_path.clone_owned(),
1514 loader_name: loader.type_path(),
1515 error: e.into(),
1516 })
1517 })
1518 }
1519
1520 pub async fn wait_for_asset<A: Asset>(
1528 &self,
1529 handle: &Handle<A>,
1532 ) -> Result<(), WaitForAssetError> {
1533 self.wait_for_asset_id(handle.id().untyped()).await
1534 }
1535
1536 pub async fn wait_for_asset_untyped(
1544 &self,
1545 handle: &UntypedHandle,
1548 ) -> Result<(), WaitForAssetError> {
1549 self.wait_for_asset_id(handle.id()).await
1550 }
1551
1552 pub async fn wait_for_asset_id(
1572 &self,
1573 id: impl Into<UntypedAssetId>,
1574 ) -> Result<(), WaitForAssetError> {
1575 let Ok(index) = id.into().try_into() else {
1576 return Err(WaitForAssetError::NotLoaded);
1578 };
1579 core::future::poll_fn(move |cx| self.wait_for_asset_id_poll_fn(cx, index)).await
1580 }
1581
1582 fn wait_for_asset_id_poll_fn(
1584 &self,
1585 cx: &mut core::task::Context<'_>,
1586 index: ErasedAssetIndex,
1587 ) -> Poll<Result<(), WaitForAssetError>> {
1588 let infos = self.read_infos();
1589
1590 let Some(info) = infos.get(index) else {
1591 return Poll::Ready(Err(WaitForAssetError::NotLoaded));
1592 };
1593
1594 match (&info.load_state, &info.rec_dep_load_state) {
1595 (LoadState::Loaded, RecursiveDependencyLoadState::Loaded) => Poll::Ready(Ok(())),
1596 (LoadState::NotLoaded, _) => Poll::Ready(Err(WaitForAssetError::NotLoaded)),
1598 (LoadState::Loading, _)
1600 | (_, RecursiveDependencyLoadState::Loading)
1601 | (LoadState::Loaded, RecursiveDependencyLoadState::NotLoaded) => {
1602 let has_waker = info
1604 .waiting_tasks
1605 .iter()
1606 .any(|waker| waker.will_wake(cx.waker()));
1607
1608 if has_waker {
1609 return Poll::Pending;
1610 }
1611
1612 let mut infos = {
1613 drop(infos);
1615 self.write_infos()
1616 };
1617
1618 let Some(info) = infos.get_mut(index) else {
1619 return Poll::Ready(Err(WaitForAssetError::NotLoaded));
1620 };
1621
1622 let is_loading = matches!(
1625 (&info.load_state, &info.rec_dep_load_state),
1626 (LoadState::Loading, _)
1627 | (_, RecursiveDependencyLoadState::Loading)
1628 | (LoadState::Loaded, RecursiveDependencyLoadState::NotLoaded)
1629 );
1630
1631 if !is_loading {
1632 cx.waker().wake_by_ref();
1633 } else {
1634 info.waiting_tasks.push(cx.waker().clone());
1636 }
1637
1638 Poll::Pending
1639 }
1640 (LoadState::Failed(error), _) => {
1641 Poll::Ready(Err(WaitForAssetError::Failed(error.clone())))
1642 }
1643 (_, RecursiveDependencyLoadState::Failed(error)) => {
1644 Poll::Ready(Err(WaitForAssetError::DependencyFailed(error.clone())))
1645 }
1646 }
1647 }
1648
1649 pub async fn write_default_loader_meta_file_for_path(
1660 &self,
1661 path: impl Into<AssetPath<'_>>,
1662 ) -> Result<(), WriteDefaultMetaError> {
1663 let path = path.into();
1664 let loader = self.get_path_asset_loader(&path).await?;
1665
1666 let meta = loader.default_meta();
1667 let serialized_meta = meta.serialize();
1668
1669 let source = self.get_source(path.source())?;
1670
1671 let reader = source.reader();
1672 match reader.read_meta_bytes(path.path()).await {
1673 Ok(_) => return Err(WriteDefaultMetaError::MetaAlreadyExists),
1674 Err(AssetReaderError::NotFound(_)) => {
1675 }
1677 Err(AssetReaderError::Io(err)) => {
1678 return Err(WriteDefaultMetaError::IoErrorFromExistingMetaCheck(err))
1679 }
1680 Err(AssetReaderError::HttpError(err)) => {
1681 return Err(WriteDefaultMetaError::HttpErrorFromExistingMetaCheck(err))
1682 }
1683 }
1684
1685 let writer = source.writer()?;
1686 writer
1687 .write_meta_bytes(path.path(), &serialized_meta)
1688 .await?;
1689
1690 Ok(())
1691 }
1692}
1693
1694pub struct LoadBuilder<'a> {
1706 asset_server: &'a AssetServer,
1708 meta_transform: Option<MetaTransform>,
1711 override_unapproved: bool,
1713 guard: Option<Box<dyn Send + Sync + 'static>>,
1715}
1716
1717impl<'a> LoadBuilder<'a> {
1718 #[must_use = "the load doesn't start until LoadBuilder has been consumed"]
1720 fn new(asset_server: &'a AssetServer) -> Self {
1721 Self {
1722 asset_server,
1723 meta_transform: None,
1724 override_unapproved: false,
1725 guard: None,
1726 }
1727 }
1728
1729 #[must_use = "the load doesn't start until LoadBuilder has been consumed"]
1737 pub fn with_settings<S: Settings>(
1738 mut self,
1739 settings: impl Fn(&mut S) + Send + Sync + 'static,
1740 ) -> Self {
1741 let new_transform = loader_settings_meta_transform(settings);
1742 if let Some(prev_transform) = self.meta_transform.take() {
1743 self.meta_transform = Some(Box::new(move |meta| {
1744 prev_transform(meta);
1745 new_transform(meta);
1746 }));
1747 } else {
1748 self.meta_transform = Some(new_transform);
1749 }
1750 self
1751 }
1752
1753 #[must_use = "the load doesn't start until LoadBuilder has been consumed"]
1757 pub fn override_unapproved(mut self) -> Self {
1758 self.override_unapproved = true;
1759 self
1760 }
1761
1762 #[must_use = "the load doesn't start until LoadBuilder has been consumed"]
1770 pub fn with_guard(mut self, guard: impl Send + Sync + 'static) -> Self {
1771 if self.guard.is_some() {
1772 warn!("Adding a second guard to a LoadBuilder drops the first guard! This is likely a mistake.");
1773 }
1774 self.guard = Some(Box::new(guard));
1777 self
1778 }
1779
1780 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
1790 pub fn load<'b, A: Asset>(self, asset_path: impl Into<AssetPath<'b>>) -> Handle<A> {
1791 self.load_typed_internal(TypeId::of::<A>(), Some(type_name::<A>()), asset_path.into())
1792 .typed_unchecked()
1793 }
1794
1795 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
1798 pub fn load_erased<'b>(
1799 self,
1800 type_id: TypeId,
1801 asset_path: impl Into<AssetPath<'b>>,
1802 ) -> UntypedHandle {
1803 self.load_typed_internal(type_id, None, asset_path.into())
1804 }
1805
1806 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
1830 pub fn load_untyped<'b>(
1831 self,
1832 asset_path: impl Into<AssetPath<'b>>,
1833 ) -> Handle<LoadedUntypedAsset> {
1834 self.asset_server.load_unknown_type_with_meta_transform(
1835 asset_path,
1836 self.meta_transform,
1837 self.guard,
1838 self.override_unapproved,
1839 )
1840 }
1841
1842 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
1851 pub async fn load_untyped_async<'b>(
1852 self,
1853 asset_path: impl Into<AssetPath<'b>>,
1854 ) -> Result<UntypedHandle, AssetLoadError> {
1855 let path: AssetPath = asset_path.into();
1856 validate_path(
1857 &path,
1858 self.override_unapproved,
1859 self.asset_server.data.unapproved_path_mode,
1860 )?;
1861
1862 self.asset_server.write_infos().stats.started_load_tasks += 1;
1863
1864 self.asset_server
1865 .load_internal(None, path, false, self.meta_transform)
1866 .await
1867 .map(|h| h.expect("handle must be returned, since we didn't pass in an input handle"))
1868 }
1869
1870 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
1872 fn load_typed_internal(
1873 self,
1874 type_id: TypeId,
1875 type_name: Option<&str>,
1876 asset_path: AssetPath<'_>,
1877 ) -> UntypedHandle {
1878 self.asset_server.load_with_meta_transform(
1879 asset_path,
1880 type_id,
1881 type_name,
1882 self.meta_transform,
1883 self.guard,
1884 self.override_unapproved,
1885 )
1886 }
1887}
1888
1889pub fn handle_internal_asset_events(world: &mut World) {
1891 world.resource_scope(|world, server: Mut<AssetServer>| {
1892 let mut infos = server.write_infos();
1893 let var_name = vec![];
1894 let mut untyped_failures = var_name;
1895 for event in server.data.asset_event_receiver.try_iter() {
1896 match event {
1897 InternalAssetEvent::Loaded {
1898 index,
1899 loaded_asset,
1900 } => {
1901 infos.process_asset_load(
1902 index,
1903 loaded_asset,
1904 world,
1905 &server.data.asset_event_sender,
1906 );
1907 }
1908 InternalAssetEvent::LoadedWithDependencies { index } => {
1909 let sender = infos
1910 .dependency_loaded_event_sender
1911 .get(&index.type_id)
1912 .expect("Asset event sender should exist");
1913 sender(world, index.index);
1914 if let Some(info) = infos.get_mut(index) {
1915 for waker in info.waiting_tasks.drain(..) {
1916 waker.wake();
1917 }
1918 }
1919 }
1920 InternalAssetEvent::Failed { index, path, error } => {
1921 infos.process_asset_fail(index, error.clone());
1922
1923 untyped_failures.push(UntypedAssetLoadFailedEvent {
1925 id: index.into(),
1926 path: path.clone(),
1927 error: error.clone(),
1928 });
1929
1930 let sender = infos
1932 .dependency_failed_event_sender
1933 .get(&index.type_id)
1934 .expect("Asset failed event sender should exist");
1935 sender(world, index.index, path, error);
1936 }
1937 }
1938 }
1939
1940 if !untyped_failures.is_empty() {
1941 world.write_message_batch(untyped_failures);
1942 }
1943
1944 if !infos.watching_for_changes {
1947 return;
1948 }
1949
1950 fn queue_ancestors(
1951 asset_path: &AssetPath,
1952 infos: &AssetInfos,
1953 paths_to_reload: &mut HashSet<AssetPath<'static>>,
1954 ) {
1955 if let Some(dependents) = infos.loader_dependents.get(asset_path) {
1956 for dependent in dependents {
1957 paths_to_reload.insert(dependent.to_owned());
1958 queue_ancestors(dependent, infos, paths_to_reload);
1959 }
1960 }
1961 }
1962
1963 let mut folders_to_reload = Vec::default();
1964 let mut reload_parent_folders =
1965 |path: &PathBuf, source: &AssetSourceId<'static>, infos: &mut AssetInfos| {
1966 let mut new_loads = 0;
1967 for parent in path.ancestors().skip(1) {
1968 let parent_asset_path =
1969 AssetPath::from(parent.to_path_buf()).with_source(source.clone());
1970 for folder_handle in infos.get_path_handles(&parent_asset_path) {
1971 info!(
1972 "Reloading folder {parent_asset_path} because the content has changed"
1973 );
1974 new_loads += 1;
1975 folders_to_reload.push((folder_handle, parent_asset_path.clone()));
1976 }
1977 }
1978 infos.stats.started_load_tasks += new_loads;
1979 };
1980
1981 let mut paths_to_reload = <HashSet<_>>::default();
1982 let mut reload_path =
1983 |path: PathBuf, source: &AssetSourceId<'static>, infos: &AssetInfos| {
1984 let path = AssetPath::from(path).with_source(source);
1985 queue_ancestors(&path, infos, &mut paths_to_reload);
1986 paths_to_reload.insert(path);
1987 };
1988
1989 let mut handle_event = |source: AssetSourceId<'static>, event: AssetSourceEvent| {
1990 match event {
1991 AssetSourceEvent::AddedAsset(path) => {
1992 reload_parent_folders(&path, &source, &mut infos);
1993 reload_path(path, &source, &infos);
1994 }
1995 AssetSourceEvent::ModifiedAsset(path) | AssetSourceEvent::ModifiedMeta(path) => {
1998 reload_path(path, &source, &infos);
1999 }
2000 AssetSourceEvent::RenamedFolder { old, new } => {
2001 reload_parent_folders(&old, &source, &mut infos);
2002 reload_parent_folders(&new, &source, &mut infos);
2003 }
2004 AssetSourceEvent::RemovedAsset(path)
2005 | AssetSourceEvent::RemovedFolder(path)
2006 | AssetSourceEvent::AddedFolder(path) => {
2007 reload_parent_folders(&path, &source, &mut infos);
2008 }
2009 _ => {}
2010 }
2011 };
2012
2013 for source in server.data.sources.iter() {
2014 match server.data.mode {
2015 AssetServerMode::Unprocessed => {
2016 if let Some(receiver) = source.event_receiver() {
2017 while let Ok(event) = receiver.try_recv() {
2018 handle_event(source.id(), event);
2019 }
2020 }
2021 }
2022 AssetServerMode::Processed => {
2023 if let Some(receiver) = source.processed_event_receiver() {
2024 while let Ok(event) = receiver.try_recv() {
2025 handle_event(source.id(), event);
2026 }
2027 }
2028 }
2029 }
2030 }
2031
2032 bevy_tasks::cfg::multi_threaded! {
2033 if {} else {
2034 drop(infos);
2037 }
2038 }
2039
2040 for (handle, path) in folders_to_reload {
2041 let index = (&handle).try_into().unwrap();
2043 server.load_folder_internal(index, path);
2044 }
2045 for path in paths_to_reload {
2046 server.reload_internal(path, true);
2047 }
2048
2049 let mut infos = bevy_tasks::cfg::multi_threaded! {
2050 if {
2051 infos
2052 } else {
2053 server.write_infos()
2055 }
2056 };
2057 infos
2058 .pending_tasks
2059 .retain(|_, load_task| !load_task.is_finished());
2060 });
2061}
2062
2063pub fn publish_asset_server_diagnostics(
2065 asset_server: Res<AssetServer>,
2066 mut diagnostics: Diagnostics,
2067) {
2068 let infos = asset_server.read_infos();
2069 diagnostics.add_measurement(&AssetServer::STARTED_LOAD_COUNT, || {
2070 infos.stats.started_load_tasks as _
2071 });
2072}
2073
2074fn validate_path(
2076 path: &AssetPath<'_>,
2077 override_unapproved: bool,
2078 unapproved_path_mode: UnapprovedPathMode,
2079) -> Result<(), AssetLoadError> {
2080 if path.path() == Path::new("") {
2081 return Err(AssetLoadError::EmptyPath(path.clone_owned()));
2082 }
2083
2084 if path.is_unapproved() {
2085 match (unapproved_path_mode, override_unapproved) {
2086 (UnapprovedPathMode::Allow, _) | (UnapprovedPathMode::Deny, true) => {}
2087 (UnapprovedPathMode::Deny, false) | (UnapprovedPathMode::Forbid, _) => {
2088 return Err(AssetLoadError::UnapprovedPath(path.clone_owned()));
2089 }
2090 }
2091 }
2092
2093 Ok(())
2094}
2095
2096pub(crate) enum InternalAssetEvent {
2098 Loaded {
2099 index: ErasedAssetIndex,
2100 loaded_asset: ErasedLoadedAsset,
2101 },
2102 LoadedWithDependencies {
2103 index: ErasedAssetIndex,
2104 },
2105 Failed {
2106 index: ErasedAssetIndex,
2107 path: AssetPath<'static>,
2108 error: AssetLoadError,
2109 },
2110}
2111
2112#[derive(Component, Clone, Debug)]
2114pub enum LoadState {
2115 NotLoaded,
2117
2118 Loading,
2120
2121 Loaded,
2123
2124 Failed(Arc<AssetLoadError>),
2128}
2129
2130impl LoadState {
2131 pub fn is_loading(&self) -> bool {
2133 matches!(self, Self::Loading)
2134 }
2135
2136 pub fn is_loaded(&self) -> bool {
2138 matches!(self, Self::Loaded)
2139 }
2140
2141 pub fn is_failed(&self) -> bool {
2143 matches!(self, Self::Failed(_))
2144 }
2145}
2146
2147#[derive(Component, Clone, Debug)]
2149pub enum DependencyLoadState {
2150 NotLoaded,
2152
2153 Loading,
2155
2156 Loaded,
2158
2159 Failed(Arc<AssetLoadError>),
2163}
2164
2165impl DependencyLoadState {
2166 pub fn is_loading(&self) -> bool {
2168 matches!(self, Self::Loading)
2169 }
2170
2171 pub fn is_loaded(&self) -> bool {
2173 matches!(self, Self::Loaded)
2174 }
2175
2176 pub fn is_failed(&self) -> bool {
2178 matches!(self, Self::Failed(_))
2179 }
2180}
2181
2182#[derive(Component, Clone, Debug)]
2184pub enum RecursiveDependencyLoadState {
2185 NotLoaded,
2187
2188 Loading,
2190
2191 Loaded,
2193
2194 Failed(Arc<AssetLoadError>),
2199}
2200
2201impl RecursiveDependencyLoadState {
2202 pub fn is_loading(&self) -> bool {
2204 matches!(self, Self::Loading)
2205 }
2206
2207 pub fn is_loaded(&self) -> bool {
2209 matches!(self, Self::Loaded)
2210 }
2211
2212 pub fn is_failed(&self) -> bool {
2214 matches!(self, Self::Failed(_))
2215 }
2216}
2217
2218#[derive(Error, Debug, Clone)]
2220#[error("Requested handle of type {requested:?} for asset '{path}' does not match actual asset type '{actual_asset_name}', which used loader '{loader_name}'")]
2221pub struct RequestedHandleTypeMismatchError {
2222 pub path: AssetPath<'static>,
2224 pub requested: TypeId,
2226 pub actual_asset_name: &'static str,
2228 pub loader_name: &'static str,
2230}
2231
2232#[derive(Error, Debug, Clone)]
2234#[expect(
2235 missing_docs,
2236 reason = "Adding docs to the variants would not add information beyond the error message and the names"
2237)]
2238pub enum AssetLoadError {
2239 #[error("Attempted to load an asset with an empty path \"{0}\".")]
2240 EmptyPath(AssetPath<'static>),
2241 #[error("Asset path \"{0}\" is unapproved. See UnapprovedPathMode for details.")]
2242 UnapprovedPath(AssetPath<'static>),
2243 #[error(transparent)]
2244 RequestedHandleTypeMismatch(#[from] Box<RequestedHandleTypeMismatchError>),
2245 #[error("Could not find an asset loader matching: Asset Type: {asset_type_id:?}; Path: {asset_path:?};")]
2246 MissingAssetLoader {
2247 asset_type_id: Option<TypeId>,
2248 asset_path: String,
2249 },
2250 #[error(transparent)]
2251 MissingAssetLoaderForExtension(#[from] MissingAssetLoaderForExtensionError),
2252 #[error(transparent)]
2253 MissingAssetLoaderForTypeName(#[from] MissingAssetLoaderForTypeNameError),
2254 #[error(transparent)]
2255 MissingAssetLoaderForTypeIdError(#[from] MissingAssetLoaderForTypeIdError),
2256 #[error(transparent)]
2257 AssetReaderError(#[from] AssetReaderError),
2258 #[error(transparent)]
2259 MissingAssetSourceError(#[from] MissingAssetSourceError),
2260 #[error(transparent)]
2261 MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError),
2262 #[error("Encountered an error while reading asset metadata bytes")]
2263 AssetMetaReadError,
2264 #[error("Failed to deserialize meta for asset {path}: {error}")]
2265 DeserializeMeta {
2266 path: AssetPath<'static>,
2267 error: Box<DeserializeMetaError>,
2268 },
2269 #[error("Asset '{path}' is configured to be processed. It cannot be loaded directly.")]
2270 #[from(ignore)]
2271 CannotLoadProcessedAsset { path: AssetPath<'static> },
2272 #[error("Asset '{path}' is configured to be ignored. It cannot be loaded.")]
2273 #[from(ignore)]
2274 CannotLoadIgnoredAsset { path: AssetPath<'static> },
2275 #[error("Failed to load asset '{path}', asset loader '{loader_name}' panicked")]
2276 AssetLoaderPanic {
2277 path: AssetPath<'static>,
2278 loader_name: &'static str,
2279 },
2280 #[error(transparent)]
2281 AssetLoaderError(#[from] AssetLoaderError),
2282 #[error(transparent)]
2283 AddAsyncError(#[from] AddAsyncError),
2284 #[error("The file at '{}' does not contain the labeled asset '{}'; it contains the following {} assets: {}",
2285 base_path,
2286 label,
2287 all_labels.len(),
2288 all_labels.iter().map(|l| format!("'{l}'")).collect::<Vec<_>>().join(", "))]
2289 MissingLabel {
2290 base_path: AssetPath<'static>,
2291 label: String,
2292 all_labels: Vec<String>,
2293 },
2294}
2295
2296#[derive(Error, Debug, Clone)]
2298#[error("Failed to load asset '{path}' with asset loader '{loader_name}': {error}")]
2299pub struct AssetLoaderError {
2300 path: AssetPath<'static>,
2301 loader_name: &'static str,
2302 error: Arc<BevyError>,
2303}
2304
2305impl AssetLoaderError {
2306 pub fn path(&self) -> &AssetPath<'static> {
2308 &self.path
2309 }
2310
2311 pub fn error(&self) -> &BevyError {
2316 &self.error
2317 }
2318}
2319
2320#[derive(Error, Debug, Clone)]
2322#[error("An error occurred while resolving an asset added by `add_async`: {error}")]
2323pub struct AddAsyncError {
2324 error: Arc<dyn core::error::Error + Send + Sync + 'static>,
2325}
2326
2327#[derive(Error, Debug, Clone, PartialEq, Eq)]
2329#[error("no `AssetLoader` found{}", format_missing_asset_ext(extensions))]
2330pub struct MissingAssetLoaderForExtensionError {
2331 extensions: Vec<String>,
2332}
2333
2334#[derive(Error, Debug, Clone, PartialEq, Eq)]
2336#[error("no `AssetLoader` found with the name '{type_name}'")]
2337pub struct MissingAssetLoaderForTypeNameError {
2338 pub type_name: String,
2340}
2341
2342#[derive(Error, Debug, Clone, PartialEq, Eq)]
2344#[error("no `AssetLoader` found with the ID '{type_id:?}'")]
2345pub struct MissingAssetLoaderForTypeIdError {
2346 pub type_id: TypeId,
2348}
2349
2350fn format_missing_asset_ext(exts: &[String]) -> String {
2351 if !exts.is_empty() {
2352 format!(
2353 " for the following extension{}: {}",
2354 if exts.len() > 1 { "s" } else { "" },
2355 exts.join(", ")
2356 )
2357 } else {
2358 " for file with no extension".to_string()
2359 }
2360}
2361
2362impl core::fmt::Debug for AssetServer {
2363 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2364 f.debug_struct("AssetServer")
2365 .field("info", &self.data.infos.read())
2366 .finish()
2367 }
2368}
2369
2370const UNTYPED_SOURCE_SUFFIX: &str = "--untyped";
2373
2374#[derive(Error, Debug, Clone)]
2376pub enum WaitForAssetError {
2377 #[error("tried to wait for an asset that is not being loaded")]
2379 NotLoaded,
2380 #[error(transparent)]
2382 Failed(Arc<AssetLoadError>),
2383 #[error(transparent)]
2385 DependencyFailed(Arc<AssetLoadError>),
2386}
2387
2388#[derive(Error, Debug)]
2389pub enum WriteDefaultMetaError {
2390 #[error(transparent)]
2391 MissingAssetLoader(#[from] MissingAssetLoaderForExtensionError),
2392 #[error(transparent)]
2393 MissingAssetSource(#[from] MissingAssetSourceError),
2394 #[error(transparent)]
2395 MissingAssetWriter(#[from] MissingAssetWriterError),
2396 #[error("failed to write default asset meta file: {0}")]
2397 FailedToWriteMeta(#[from] AssetWriterError),
2398 #[error("asset meta file already exists, so avoiding overwrite")]
2399 MetaAlreadyExists,
2400 #[error("encountered an I/O error while reading the existing meta file: {0}")]
2401 IoErrorFromExistingMetaCheck(Arc<std::io::Error>),
2402 #[error("encountered HTTP status {0} when reading the existing meta file")]
2403 HttpErrorFromExistingMetaCheck(u16),
2404}