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 #[deprecated(
381 note = "Use `asset_server.load_builder().override_unapproved().load(path)` instead"
382 )]
383 pub fn load_override<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Handle<A> {
384 self.load_builder().override_unapproved().load(path.into())
385 }
386
387 #[deprecated(note = "Use `asset_server.load_builder().load_erased(type_id, path)` instead")]
390 pub fn load_erased<'a>(
391 &self,
392 type_id: TypeId,
393 path: impl Into<AssetPath<'a>>,
394 ) -> UntypedHandle {
395 self.load_builder().load_erased(type_id, path.into())
396 }
397
398 #[deprecated(note = "Use `asset_server.load_builder().with_guard(guard).load(path)` instead")]
414 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
415 pub fn load_acquire<'a, A: Asset, G: Send + Sync + 'static>(
416 &self,
417 path: impl Into<AssetPath<'a>>,
418 guard: G,
419 ) -> Handle<A> {
420 self.load_builder().with_guard(guard).load(path.into())
421 }
422
423 #[deprecated(
429 note = "Use `asset_server.load_builder().with_guard(guard).override_unapproved().load(path)` instead"
430 )]
431 pub fn load_acquire_override<'a, A: Asset, G: Send + Sync + 'static>(
432 &self,
433 path: impl Into<AssetPath<'a>>,
434 guard: G,
435 ) -> Handle<A> {
436 self.load_builder()
437 .with_guard(guard)
438 .override_unapproved()
439 .load(path.into())
440 }
441
442 #[deprecated(
446 note = "Use `asset_server.load_builder().with_settings(settings).load(path)` instead"
447 )]
448 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
449 pub fn load_with_settings<'a, A: Asset, S: Settings>(
450 &self,
451 path: impl Into<AssetPath<'a>>,
452 settings: impl Fn(&mut S) + Send + Sync + 'static,
453 ) -> Handle<A> {
454 self.load_builder()
455 .with_settings(settings)
456 .load(path.into())
457 }
458
459 #[deprecated(
465 note = "Use `asset_server.load_builder().with_settings(settings).override_unapproved().load(path)` instead"
466 )]
467 pub fn load_with_settings_override<'a, A: Asset, S: Settings>(
468 &self,
469 path: impl Into<AssetPath<'a>>,
470 settings: impl Fn(&mut S) + Send + Sync + 'static,
471 ) -> Handle<A> {
472 self.load_builder()
473 .with_settings(settings)
474 .override_unapproved()
475 .load(path.into())
476 }
477
478 #[deprecated(
488 note = "Use `asset_server.load_builder().with_guard(guard).with_settings(settings).load(path)` instead"
489 )]
490 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
491 pub fn load_acquire_with_settings<'a, A: Asset, S: Settings, G: Send + Sync + 'static>(
492 &self,
493 path: impl Into<AssetPath<'a>>,
494 settings: impl Fn(&mut S) + Send + Sync + 'static,
495 guard: G,
496 ) -> Handle<A> {
497 self.load_builder()
498 .with_guard(guard)
499 .with_settings(settings)
500 .load(path.into())
501 }
502
503 #[deprecated(
509 note = "Use `asset_server.load_builder().with_guard(guard).with_settings(settings).override_unapproved().load(path)` instead"
510 )]
511 pub fn load_acquire_with_settings_override<
512 'a,
513 A: Asset,
514 S: Settings,
515 G: Send + Sync + 'static,
516 >(
517 &self,
518 path: impl Into<AssetPath<'a>>,
519 settings: impl Fn(&mut S) + Send + Sync + 'static,
520 guard: G,
521 ) -> Handle<A> {
522 self.load_builder()
523 .with_guard(guard)
524 .with_settings(settings)
525 .override_unapproved()
526 .load(path.into())
527 }
528
529 pub(crate) fn load_with_meta_transform<'a, G: Send + Sync + 'static>(
530 &self,
531 path: impl Into<AssetPath<'a>>,
532 type_id: TypeId,
533 type_name: Option<&str>,
534 meta_transform: Option<MetaTransform>,
535 guard: G,
536 override_unapproved: bool,
537 ) -> UntypedHandle {
538 let path = path.into().into_owned();
539 if path.path() == Path::new("") {
540 error!("Attempted to load an asset with an empty path \"{path}\"!");
541 return UntypedHandle::default_for_type(type_id);
542 }
543
544 if path.is_unapproved() {
545 match (&self.data.unapproved_path_mode, override_unapproved) {
546 (UnapprovedPathMode::Allow, _) | (UnapprovedPathMode::Deny, true) => {}
547 (UnapprovedPathMode::Deny, false) | (UnapprovedPathMode::Forbid, _) => {
548 error!("Asset path {path} is unapproved. See UnapprovedPathMode for details.");
549 return UntypedHandle::Uuid {
550 type_id,
551 uuid: AssetId::<()>::DEFAULT_UUID,
552 };
553 }
554 }
555 }
556
557 let mut infos = self.write_infos();
558 let (handle, should_load) = infos.get_or_create_path_handle_erased(
559 path.clone(),
560 type_id,
561 type_name,
562 HandleLoadingMode::Request,
563 meta_transform,
564 );
565
566 if should_load {
567 self.spawn_load_task(handle.clone(), path, infos, guard);
568 }
569
570 handle
571 }
572
573 pub(crate) fn spawn_load_task<G: Send + Sync + 'static>(
574 &self,
575 handle: UntypedHandle,
576 path: AssetPath<'static>,
577 mut infos: RwLockWriteGuard<AssetInfos>,
578 guard: G,
579 ) {
580 infos.stats.started_load_tasks += 1;
581
582 #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
584 drop(infos);
585
586 let owned_handle = handle.clone();
587 let server = self.clone();
588 let task = IoTaskPool::get().spawn(async move {
589 if let Err(err) = server
590 .load_internal(Some(owned_handle), path, false, None)
591 .await
592 {
593 error!("{}", err);
594 }
595 drop(guard);
596 });
597
598 #[cfg(not(any(target_arch = "wasm32", not(feature = "multi_threaded"))))]
599 {
600 let mut infos = infos;
601 infos
602 .pending_tasks
603 .insert((&handle).try_into().unwrap(), task);
604 }
605
606 #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
607 task.detach();
608 }
609
610 #[deprecated(note = "Use `asset_server.load_builder().load_untyped_async(path)` instead")]
614 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
615 pub async fn load_untyped_async<'a>(
616 &self,
617 path: impl Into<AssetPath<'a>>,
618 ) -> Result<UntypedHandle, AssetLoadError> {
619 self.load_builder().load_untyped_async(path.into()).await
620 }
621
622 pub(crate) fn load_unknown_type_with_meta_transform<'a, G: Send + Sync + 'static>(
623 &self,
624 path: impl Into<AssetPath<'a>>,
625 meta_transform: Option<MetaTransform>,
626 guard: G,
627 override_unapproved: bool,
628 ) -> Handle<LoadedUntypedAsset> {
629 let path = path.into().into_owned();
630 if path.path() == Path::new("") {
631 error!("Attempted to load an asset with an empty path \"{path}\"!");
632 return Handle::default();
633 }
634
635 if path.is_unapproved() {
636 match (&self.data.unapproved_path_mode, override_unapproved) {
637 (UnapprovedPathMode::Allow, _) | (UnapprovedPathMode::Deny, true) => {}
638 (UnapprovedPathMode::Deny, false) | (UnapprovedPathMode::Forbid, _) => {
639 error!("Asset path {path} is unapproved. See UnapprovedPathMode for details.");
640 return Handle::default();
641 }
642 }
643 }
644
645 let untyped_source = AssetSourceId::Name(match path.source() {
646 AssetSourceId::Default => CowArc::Static(UNTYPED_SOURCE_SUFFIX),
647 AssetSourceId::Name(source) => {
648 CowArc::Owned(format!("{source}--{UNTYPED_SOURCE_SUFFIX}").into())
649 }
650 });
651 let mut infos = self.write_infos();
652 let (handle, should_load) = infos.get_or_create_path_handle::<LoadedUntypedAsset>(
653 path.clone().with_source(untyped_source),
654 HandleLoadingMode::Request,
655 meta_transform,
656 );
657
658 if !should_load {
659 return handle;
660 }
661 let index = (&handle).try_into().unwrap();
662
663 infos.stats.started_load_tasks += 1;
664
665 #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
667 drop(infos);
668
669 let server = self.clone();
670 let task = IoTaskPool::get().spawn(async move {
671 let path_clone = path.clone();
672 match server
673 .load_internal(None, path, false, None)
674 .await
675 .map(|h| {
676 h.expect("handle must be returned, since we didn't pass in an input handle")
677 }) {
678 Ok(handle) => server.send_asset_event(InternalAssetEvent::Loaded {
679 index,
680 loaded_asset: LoadedAsset::new_with_dependencies(LoadedUntypedAsset { handle })
681 .into(),
682 }),
683 Err(err) => {
684 error!("{err}");
685 server.send_asset_event(InternalAssetEvent::Failed {
686 index,
687 path: path_clone,
688 error: err,
689 });
690 }
691 };
692 drop(guard);
693 });
694
695 #[cfg(not(any(target_arch = "wasm32", not(feature = "multi_threaded"))))]
696 infos.pending_tasks.insert(index, task);
697
698 #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
699 task.detach();
700
701 handle
702 }
703
704 #[deprecated(note = "Use `asset_server.load_builder().load_untyped(path)` instead")]
728 #[must_use = "not using the returned strong handle may result in the unexpected release of the assets"]
729 pub fn load_untyped<'a>(&self, path: impl Into<AssetPath<'a>>) -> Handle<LoadedUntypedAsset> {
730 self.load_builder().load_untyped(path.into())
731 }
732
733 async fn load_internal<'a>(
742 &self,
743 input_handle: Option<UntypedHandle>,
744 path: AssetPath<'a>,
745 force: bool,
746 meta_transform: Option<MetaTransform>,
747 ) -> Result<Option<UntypedHandle>, AssetLoadError> {
748 let input_handle_type_id = input_handle.as_ref().map(UntypedHandle::type_id);
749
750 let path = path.into_owned();
751 let path_clone = path.clone();
752 let (mut meta, loader, mut reader) = self
753 .get_meta_loader_and_reader(&path_clone, input_handle_type_id)
754 .await
755 .inspect_err(|e| {
756 if let Some(handle) = &input_handle {
759 self.send_asset_event(InternalAssetEvent::Failed {
760 index: handle.try_into().unwrap(),
761 path: path.clone_owned(),
762 error: e.clone(),
763 });
764 }
765 })?;
766
767 if let Some(meta_transform) = input_handle.as_ref().and_then(|h| h.meta_transform()) {
768 (*meta_transform)(&mut *meta);
769 }
770
771 let asset_id: Option<ErasedAssetIndex>; let fetched_handle; let should_load; if let Some(input_handle) = input_handle {
775 asset_id = Some((&input_handle).try_into().unwrap());
778 fetched_handle = None;
781 should_load = true;
783 } else {
784 let mut infos = self.write_infos();
790 let result = infos.get_or_create_path_handle_internal(
791 path.clone(),
792 path.label().is_none().then(|| loader.asset_type_id()),
793 HandleLoadingMode::Request,
794 meta_transform,
795 );
796 match unwrap_with_context(
797 result,
798 loader.asset_type_id(),
799 Some(loader.asset_type_name()),
800 ) {
801 None => {
804 asset_id = None;
807 fetched_handle = None;
808 should_load = true;
811 }
812 Some((handle, result_should_load)) => {
813 asset_id = Some((&handle).try_into().unwrap());
816 fetched_handle = Some(handle);
817 should_load = result_should_load;
818 }
819 }
820 }
821 if let Some(asset_type_id) = asset_id.map(|id| id.type_id) {
823 if path.label().is_none() && asset_type_id != loader.asset_type_id() {
826 error!(
827 "Expected {:?}, got {:?}",
828 asset_type_id,
829 loader.asset_type_id()
830 );
831 return Err(AssetLoadError::RequestedHandleTypeMismatch {
832 path: path.into_owned(),
833 requested: asset_type_id,
834 actual_asset_name: loader.asset_type_name(),
835 loader_name: loader.type_path(),
836 });
837 }
838 }
839 if !should_load && !force {
841 return Ok(fetched_handle);
842 }
843
844 let (base_asset_id, _base_handle, base_path) = if path.label().is_some() {
848 let mut infos = self.write_infos();
849 let base_path = path.without_label().into_owned();
850 let base_handle = infos
851 .get_or_create_path_handle_erased(
852 base_path.clone(),
853 loader.asset_type_id(),
854 Some(loader.asset_type_name()),
855 HandleLoadingMode::Force,
856 None,
857 )
858 .0;
859 (
860 (&base_handle).try_into().unwrap(),
863 Some(base_handle),
864 base_path,
865 )
866 } else {
867 (asset_id.unwrap(), None, path.clone())
868 };
869
870 match self
871 .load_with_settings_loader_and_reader(
872 &base_path,
873 meta.loader_settings().expect("meta is set to Load"),
874 &*loader,
875 &mut *reader,
876 true,
877 false,
878 )
879 .await
880 {
881 Ok(loaded_asset) => {
882 let final_handle = if let Some(label) = path.label_cow() {
883 match loaded_asset.label_to_asset_index.get(&label) {
884 Some(labeled_asset) => {
885 let labeled_asset = &loaded_asset.labeled_assets[*labeled_asset];
886 if let Some(asset_id) = asset_id
889 && asset_id.type_id != labeled_asset.handle.type_id()
890 {
891 let error = AssetLoadError::RequestedHandleTypeMismatch {
892 path: path.clone(),
893 requested: asset_id.type_id,
894 actual_asset_name: labeled_asset.asset.value.asset_type_name(),
895 loader_name: loader.type_path(),
896 };
897 self.send_asset_event(InternalAssetEvent::Failed {
898 index: asset_id,
899 error: error.clone(),
900 path: path.into_owned(),
901 });
902 return Err(error);
903 }
904 Some(labeled_asset.handle.clone())
905 }
906 None => {
907 let mut all_labels: Vec<String> = loaded_asset
908 .label_to_asset_index
909 .keys()
910 .map(|s| (**s).to_owned())
911 .collect();
912 all_labels.sort_unstable();
913 let error = AssetLoadError::MissingLabel {
914 base_path,
915 label: label.to_string(),
916 all_labels,
917 };
918 if let Some(asset_id) = asset_id {
919 self.send_asset_event(InternalAssetEvent::Failed {
920 index: asset_id,
921 error: error.clone(),
922 path: path.into_owned(),
923 });
924 }
925 return Err(error);
926 }
927 }
928 } else {
929 fetched_handle
930 };
931
932 self.send_asset_event(InternalAssetEvent::Loaded {
933 index: base_asset_id,
934 loaded_asset,
935 });
936 Ok(final_handle)
937 }
938 Err(err) => {
939 if let Some(asset_id) = asset_id {
940 self.send_asset_event(InternalAssetEvent::Failed {
941 index: asset_id,
942 error: err.clone(),
943 path: path.into_owned(),
944 });
945 }
946 Err(err)
947 }
948 }
949 }
950
951 pub fn reload<'a>(&self, path: impl Into<AssetPath<'a>>) {
953 self.reload_internal(path, false);
954 }
955
956 fn reload_internal<'a>(&self, path: impl Into<AssetPath<'a>>, log: bool) {
957 let server = self.clone();
958 let path = path.into().into_owned();
959 IoTaskPool::get()
960 .spawn(async move {
961 let mut reloaded = false;
962
963 let requests = server
966 .read_infos()
967 .get_path_handles(&path)
968 .map(|handle| server.load_internal(Some(handle), path.clone(), true, None))
969 .collect::<Vec<_>>();
970
971 for result in requests {
972 server.write_infos().stats.started_load_tasks += 1;
974 match result.await {
975 Ok(_) => reloaded = true,
976 Err(err) => error!("{}", err),
977 }
978 }
979
980 if !reloaded && server.read_infos().should_reload(&path) {
989 server.write_infos().stats.started_load_tasks += 1;
990 match server.load_internal(None, path.clone(), true, None).await {
991 Ok(_) => reloaded = true,
992 Err(err) => error!("{}", err),
993 }
994 }
995
996 if log && reloaded {
997 info!("Reloaded {}", path);
998 }
999 })
1000 .detach();
1001 }
1002
1003 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
1008 pub fn add<A: Asset>(&self, asset: A) -> Handle<A> {
1009 self.load_asset(LoadedAsset::new_with_dependencies(asset))
1010 }
1011
1012 pub(crate) fn load_asset<A: Asset>(&self, asset: impl Into<LoadedAsset<A>>) -> Handle<A> {
1013 let loaded_asset: LoadedAsset<A> = asset.into();
1014 let erased_loaded_asset: ErasedLoadedAsset = loaded_asset.into();
1015 self.load_asset_untyped(None, erased_loaded_asset)
1016 .typed_debug_checked()
1017 }
1018
1019 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
1020 pub(crate) fn load_asset_untyped(
1021 &self,
1022 path: Option<AssetPath<'static>>,
1023 asset: impl Into<ErasedLoadedAsset>,
1024 ) -> UntypedHandle {
1025 let loaded_asset = asset.into();
1026 let handle = if let Some(path) = path {
1027 let (handle, _) = self.write_infos().get_or_create_path_handle_erased(
1028 path,
1029 loaded_asset.asset_type_id(),
1030 Some(loaded_asset.asset_type_name()),
1031 HandleLoadingMode::NotLoading,
1032 None,
1033 );
1034 handle
1035 } else {
1036 self.write_infos().create_loading_handle_untyped(
1037 loaded_asset.asset_type_id(),
1038 loaded_asset.asset_type_name(),
1039 )
1040 };
1041 self.send_asset_event(InternalAssetEvent::Loaded {
1042 index: (&handle).try_into().unwrap(),
1044 loaded_asset,
1045 });
1046 handle
1047 }
1048
1049 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
1054 pub fn add_async<A: Asset, E: core::error::Error + Send + Sync + 'static>(
1055 &self,
1056 future: impl Future<Output = Result<A, E>> + Send + 'static,
1057 ) -> Handle<A> {
1058 let mut infos = self.write_infos();
1059 let handle = infos.create_loading_handle_untyped(TypeId::of::<A>(), type_name::<A>());
1060
1061 #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
1063 drop(infos);
1064
1065 let index = (&handle).try_into().unwrap();
1067
1068 let event_sender = self.data.asset_event_sender.clone();
1069
1070 let task = IoTaskPool::get().spawn(async move {
1071 match future.await {
1072 Ok(asset) => {
1073 let loaded_asset = LoadedAsset::new_with_dependencies(asset).into();
1074 event_sender
1075 .send(InternalAssetEvent::Loaded {
1076 index,
1077 loaded_asset,
1078 })
1079 .unwrap();
1080 }
1081 Err(error) => {
1082 let error = AddAsyncError {
1083 error: Arc::new(error),
1084 };
1085 error!("{error}");
1086 event_sender
1087 .send(InternalAssetEvent::Failed {
1088 index,
1089 path: Default::default(),
1090 error: AssetLoadError::AddAsyncError(error),
1091 })
1092 .unwrap();
1093 }
1094 }
1095 });
1096
1097 #[cfg(not(any(target_arch = "wasm32", not(feature = "multi_threaded"))))]
1098 infos.pending_tasks.insert(index, task);
1099
1100 #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
1101 task.detach();
1102
1103 handle.typed_debug_checked()
1104 }
1105
1106 #[must_use = "not using the returned strong handle may result in the unexpected release of the assets"]
1115 pub fn load_folder<'a>(&self, path: impl Into<AssetPath<'a>>) -> Handle<LoadedFolder> {
1116 let path = path.into().into_owned();
1117 let (handle, should_load) = self.write_infos().get_or_create_path_handle(
1118 path.clone(),
1119 HandleLoadingMode::Request,
1120 None,
1121 );
1122 if !should_load {
1123 return handle;
1124 }
1125 let index = (&handle).try_into().unwrap();
1127 self.write_infos().stats.started_load_tasks += 1;
1128 self.load_folder_internal(index, path);
1129
1130 handle
1131 }
1132
1133 pub(crate) fn load_folder_internal(&self, index: ErasedAssetIndex, path: AssetPath) {
1134 async fn load_folder<'a>(
1135 source: AssetSourceId<'static>,
1136 path: &'a Path,
1137 reader: &'a dyn ErasedAssetReader,
1138 server: &'a AssetServer,
1139 handles: &'a mut Vec<UntypedHandle>,
1140 ) -> Result<(), AssetLoadError> {
1141 let is_dir = reader.is_directory(path).await?;
1142 if is_dir {
1143 let mut path_stream = reader.read_directory(path.as_ref()).await?;
1144 while let Some(child_path) = path_stream.next().await {
1145 if reader.is_directory(&child_path).await? {
1146 Box::pin(load_folder(
1147 source.clone(),
1148 &child_path,
1149 reader,
1150 server,
1151 handles,
1152 ))
1153 .await?;
1154 } else {
1155 let path = child_path.to_str().expect("Path should be a valid string.");
1156 let asset_path = AssetPath::parse(path).with_source(source.clone());
1157 match server.load_builder().load_untyped_async(asset_path).await {
1158 Ok(handle) => handles.push(handle),
1159 Err(
1161 AssetLoadError::MissingAssetLoaderForTypeName(_)
1162 | AssetLoadError::MissingAssetLoaderForExtension(_),
1163 ) => {}
1164 Err(err) => return Err(err),
1165 }
1166 }
1167 }
1168 }
1169 Ok(())
1170 }
1171
1172 let path = path.into_owned();
1173 let server = self.clone();
1174 IoTaskPool::get()
1175 .spawn(async move {
1176 let Ok(source) = server.get_source(path.source()) else {
1177 error!(
1178 "Failed to load {path}. AssetSource {} does not exist",
1179 path.source()
1180 );
1181 return;
1182 };
1183
1184 let asset_reader = match server.data.mode {
1185 AssetServerMode::Unprocessed => source.reader(),
1186 AssetServerMode::Processed => match source.processed_reader() {
1187 Ok(reader) => reader,
1188 Err(_) => {
1189 error!(
1190 "Failed to load {path}. AssetSource {} does not have a processed AssetReader",
1191 path.source()
1192 );
1193 return;
1194 }
1195 },
1196 };
1197
1198 let mut handles = Vec::new();
1199 match load_folder(source.id(), path.path(), asset_reader, &server, &mut handles).await {
1200 Ok(_) => server.send_asset_event(InternalAssetEvent::Loaded {
1201 index,
1202 loaded_asset: LoadedAsset::new_with_dependencies(
1203 LoadedFolder { handles },
1204 )
1205 .into(),
1206 }),
1207 Err(err) => {
1208 error!("Failed to load folder. {err}");
1209 server.send_asset_event(InternalAssetEvent::Failed { index, error: err, path });
1210 },
1211 }
1212 })
1213 .detach();
1214 }
1215
1216 fn send_asset_event(&self, event: InternalAssetEvent) {
1217 self.data.asset_event_sender.send(event).unwrap();
1218 }
1219
1220 pub fn get_load_states(
1222 &self,
1223 id: impl Into<UntypedAssetId>,
1224 ) -> Option<(LoadState, DependencyLoadState, RecursiveDependencyLoadState)> {
1225 let Ok(index) = id.into().try_into() else {
1226 return None;
1228 };
1229 self.read_infos().get(index).map(|i| {
1230 (
1231 i.load_state.clone(),
1232 i.dep_load_state.clone(),
1233 i.rec_dep_load_state.clone(),
1234 )
1235 })
1236 }
1237
1238 pub fn get_load_state(&self, id: impl Into<UntypedAssetId>) -> Option<LoadState> {
1244 let Ok(index) = id.into().try_into() else {
1245 return None;
1247 };
1248 self.read_infos().get(index).map(|i| i.load_state.clone())
1249 }
1250
1251 pub fn get_dependency_load_state(
1257 &self,
1258 id: impl Into<UntypedAssetId>,
1259 ) -> Option<DependencyLoadState> {
1260 let Ok(index) = id.into().try_into() else {
1261 return None;
1263 };
1264 self.read_infos()
1265 .get(index)
1266 .map(|i| i.dep_load_state.clone())
1267 }
1268
1269 pub fn get_recursive_dependency_load_state(
1275 &self,
1276 id: impl Into<UntypedAssetId>,
1277 ) -> Option<RecursiveDependencyLoadState> {
1278 let Ok(index) = id.into().try_into() else {
1279 return None;
1281 };
1282 self.read_infos()
1283 .get(index)
1284 .map(|i| i.rec_dep_load_state.clone())
1285 }
1286
1287 pub fn load_state(&self, id: impl Into<UntypedAssetId>) -> LoadState {
1292 self.get_load_state(id).unwrap_or(LoadState::NotLoaded)
1293 }
1294
1295 pub fn dependency_load_state(&self, id: impl Into<UntypedAssetId>) -> DependencyLoadState {
1300 self.get_dependency_load_state(id)
1301 .unwrap_or(DependencyLoadState::NotLoaded)
1302 }
1303
1304 pub fn recursive_dependency_load_state(
1309 &self,
1310 id: impl Into<UntypedAssetId>,
1311 ) -> RecursiveDependencyLoadState {
1312 self.get_recursive_dependency_load_state(id)
1313 .unwrap_or(RecursiveDependencyLoadState::NotLoaded)
1314 }
1315
1316 pub fn is_loaded(&self, id: impl Into<UntypedAssetId>) -> bool {
1318 matches!(self.load_state(id), LoadState::Loaded)
1319 }
1320
1321 pub fn is_loaded_with_direct_dependencies(&self, id: impl Into<UntypedAssetId>) -> bool {
1323 matches!(
1324 self.get_load_states(id),
1325 Some((LoadState::Loaded, DependencyLoadState::Loaded, _))
1326 )
1327 }
1328
1329 pub fn is_loaded_with_dependencies(&self, id: impl Into<UntypedAssetId>) -> bool {
1332 matches!(
1333 self.get_load_states(id),
1334 Some((
1335 LoadState::Loaded,
1336 DependencyLoadState::Loaded,
1337 RecursiveDependencyLoadState::Loaded
1338 ))
1339 )
1340 }
1341
1342 pub fn are_dependencies_loaded(&self, value: &impl VisitAssetDependencies) -> bool {
1346 let infos = self.read_infos();
1347 let mut loaded = true;
1348 value.visit_dependencies(&mut |asset_id| {
1349 let index = match asset_id {
1350 UntypedAssetId::Uuid { .. } => return,
1352 UntypedAssetId::Index { type_id, index } => ErasedAssetIndex::new(index, type_id),
1353 };
1354 let Some(info) = infos.get(index) else {
1355 loaded = false;
1357 return;
1358 };
1359
1360 if !info.rec_dep_load_state.is_loaded() {
1361 loaded = false;
1362 }
1363 });
1364 loaded
1365 }
1366
1367 pub fn are_direct_dependencies_loaded(&self, value: &impl VisitAssetDependencies) -> bool {
1371 let infos = self.read_infos();
1372 let mut loaded = true;
1373 value.visit_dependencies(&mut |asset_id| {
1374 let index = match asset_id {
1375 UntypedAssetId::Uuid { .. } => return,
1377 UntypedAssetId::Index { type_id, index } => ErasedAssetIndex::new(index, type_id),
1378 };
1379 let Some(info) = infos.get(index) else {
1380 loaded = false;
1382 return;
1383 };
1384
1385 if !info.dep_load_state.is_loaded() {
1386 loaded = false;
1387 }
1388 });
1389 loaded
1390 }
1391
1392 pub fn get_handle<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Option<Handle<A>> {
1395 self.get_path_and_type_id_handle(&path.into(), TypeId::of::<A>())
1396 .map(UntypedHandle::typed_debug_checked)
1397 }
1398
1399 pub fn get_id_handle<A: Asset>(&self, id: AssetId<A>) -> Option<Handle<A>> {
1407 self.get_id_handle_untyped(id.untyped())
1408 .map(UntypedHandle::typed)
1409 }
1410
1411 pub fn get_id_handle_untyped(&self, id: UntypedAssetId) -> Option<UntypedHandle> {
1414 let Ok(index) = id.try_into() else {
1415 return None;
1417 };
1418 self.read_infos().get_index_handle(index)
1419 }
1420
1421 pub fn is_managed(&self, id: impl Into<UntypedAssetId>) -> bool {
1424 let Ok(index) = id.into().try_into() else {
1425 return false;
1427 };
1428 self.read_infos().contains_key(index)
1429 }
1430
1431 pub fn get_path_id<'a>(&self, path: impl Into<AssetPath<'a>>) -> Option<UntypedAssetId> {
1438 let infos = self.read_infos();
1439 let path = path.into();
1440 let mut ids = infos.get_path_indices(&path);
1441 ids.next().map(Into::into)
1442 }
1443
1444 pub fn get_path_ids<'a>(&self, path: impl Into<AssetPath<'a>>) -> Vec<UntypedAssetId> {
1448 let path = path.into();
1449 self.read_infos()
1450 .get_path_indices(&path)
1451 .map(Into::into)
1452 .collect()
1453 }
1454
1455 pub fn get_handle_untyped<'a>(&self, path: impl Into<AssetPath<'a>>) -> Option<UntypedHandle> {
1462 let path = path.into();
1463 self.read_infos().get_path_handles(&path).next()
1464 }
1465
1466 pub fn get_handles_untyped<'a>(&self, path: impl Into<AssetPath<'a>>) -> Vec<UntypedHandle> {
1470 let path = path.into();
1471 self.read_infos().get_path_handles(&path).collect()
1472 }
1473
1474 pub fn get_path_and_type_id_handle(
1477 &self,
1478 path: &AssetPath,
1479 type_id: TypeId,
1480 ) -> Option<UntypedHandle> {
1481 let path = path.into();
1482 self.read_infos()
1483 .get_path_and_type_id_handle(&path, type_id)
1484 }
1485
1486 pub fn get_path(&self, id: impl Into<UntypedAssetId>) -> Option<AssetPath<'_>> {
1488 let Ok(index) = id.into().try_into() else {
1489 return None;
1491 };
1492 let infos = self.read_infos();
1493 let info = infos.get(index)?;
1494 Some(info.path.as_ref()?.clone())
1495 }
1496
1497 pub fn mode(&self) -> AssetServerMode {
1499 self.data.mode
1500 }
1501
1502 pub fn preregister_loader<L: AssetLoader>(&self, extensions: &[&str]) {
1507 self.write_loaders().reserve::<L>(extensions);
1508 }
1509
1510 pub(crate) fn get_or_create_path_handle<'a, A: Asset>(
1512 &self,
1513 path: impl Into<AssetPath<'a>>,
1514 meta_transform: Option<MetaTransform>,
1515 ) -> Handle<A> {
1516 self.get_or_create_path_handle_erased(
1517 path.into().into_owned(),
1518 TypeId::of::<A>(),
1519 Some(type_name::<A>()),
1520 meta_transform,
1521 )
1522 .typed_unchecked()
1523 }
1524
1525 pub(crate) fn get_or_create_path_handle_erased<'a>(
1530 &self,
1531 path: impl Into<AssetPath<'a>>,
1532 type_id: TypeId,
1533 type_name: Option<&str>,
1534 meta_transform: Option<MetaTransform>,
1535 ) -> UntypedHandle {
1536 self.write_infos()
1537 .get_or_create_path_handle_erased(
1538 path.into().into_owned(),
1539 type_id,
1540 type_name,
1541 HandleLoadingMode::NotLoading,
1542 meta_transform,
1543 )
1544 .0
1545 }
1546
1547 pub(crate) async fn get_meta_loader_and_reader<'a>(
1548 &'a self,
1549 asset_path: &'a AssetPath<'_>,
1550 asset_type_id: Option<TypeId>,
1551 ) -> Result<
1552 (
1553 Box<dyn AssetMetaDyn>,
1554 Arc<dyn ErasedAssetLoader>,
1555 Box<dyn Reader + 'a>,
1556 ),
1557 AssetLoadError,
1558 > {
1559 let source = self.get_source(asset_path.source())?;
1560 let asset_reader = match self.data.mode {
1561 AssetServerMode::Unprocessed => source.reader(),
1562 AssetServerMode::Processed => source.processed_reader()?,
1563 };
1564 let read_meta = match &self.data.meta_check {
1565 AssetMetaCheck::Always => true,
1566 AssetMetaCheck::Paths(paths) => paths.contains(asset_path),
1567 AssetMetaCheck::Never => false,
1568 };
1569
1570 let mut meta_reader;
1575
1576 let (meta, loader) = if read_meta {
1577 match asset_reader.read_meta(asset_path.path()).await {
1578 Ok(new_meta_reader) => {
1579 meta_reader = new_meta_reader;
1580 let mut meta_bytes = vec![];
1581 meta_reader
1582 .read_to_end(&mut meta_bytes)
1583 .await
1584 .map_err(|err| AssetLoadError::AssetReaderError(err.into()))?;
1585 let minimal: AssetMetaMinimal =
1587 ron::de::from_bytes(&meta_bytes).map_err(|e| {
1588 AssetLoadError::DeserializeMeta {
1589 path: asset_path.clone_owned(),
1590 error: DeserializeMetaError::DeserializeMinimal(e).into(),
1591 }
1592 })?;
1593 let loader_name = match minimal.asset {
1594 AssetActionMinimal::Load { loader } => loader,
1595 AssetActionMinimal::Process { .. } => {
1596 return Err(AssetLoadError::CannotLoadProcessedAsset {
1597 path: asset_path.clone_owned(),
1598 })
1599 }
1600 AssetActionMinimal::Ignore => {
1601 return Err(AssetLoadError::CannotLoadIgnoredAsset {
1602 path: asset_path.clone_owned(),
1603 })
1604 }
1605 };
1606 let loader = self.get_asset_loader_with_type_name(&loader_name).await?;
1607 let meta = loader.deserialize_meta(&meta_bytes).map_err(|e| {
1608 AssetLoadError::DeserializeMeta {
1609 path: asset_path.clone_owned(),
1610 error: e.into(),
1611 }
1612 })?;
1613
1614 (meta, loader)
1615 }
1616 Err(AssetReaderError::NotFound(_)) => {
1617 let loader = { self.read_loaders().find(asset_type_id, asset_path) };
1619
1620 let error = || AssetLoadError::MissingAssetLoader {
1621 asset_type_id,
1622 asset_path: asset_path.to_string(),
1623 };
1624
1625 let loader = loader.ok_or_else(error)?.get().await.map_err(|_| error())?;
1626
1627 let meta = loader.default_meta();
1628 (meta, loader)
1629 }
1630 Err(err) => return Err(err.into()),
1631 }
1632 } else {
1633 let loader = { self.read_loaders().find(asset_type_id, asset_path) };
1634
1635 let error = || AssetLoadError::MissingAssetLoader {
1636 asset_type_id,
1637 asset_path: asset_path.to_string(),
1638 };
1639
1640 let loader = loader.ok_or_else(error)?.get().await.map_err(|_| error())?;
1641
1642 let meta = loader.default_meta();
1643 (meta, loader)
1644 };
1645 let reader = asset_reader.read(asset_path.path()).await?;
1646 Ok((meta, loader, reader))
1647 }
1648
1649 pub(crate) async fn load_with_settings_loader_and_reader(
1650 &self,
1651 asset_path: &AssetPath<'_>,
1652 settings: &dyn Settings,
1653 loader: &dyn ErasedAssetLoader,
1654 reader: &mut dyn Reader,
1655 load_dependencies: bool,
1656 populate_hashes: bool,
1657 ) -> Result<ErasedLoadedAsset, AssetLoadError> {
1658 let asset_path = asset_path.clone_owned();
1660 let load_context =
1661 LoadContext::new(self, asset_path.clone(), load_dependencies, populate_hashes);
1662 let load = AssertUnwindSafe(loader.load(reader, settings, load_context)).catch_unwind();
1663 #[cfg(feature = "trace")]
1664 let load = {
1665 use tracing::Instrument;
1666
1667 let span = tracing::info_span!(
1668 "asset loading",
1669 loader = loader.type_path(),
1670 asset = asset_path.to_string()
1671 );
1672 load.instrument(span)
1673 };
1674 load.await
1675 .map_err(|_| AssetLoadError::AssetLoaderPanic {
1676 path: asset_path.clone_owned(),
1677 loader_name: loader.type_path(),
1678 })?
1679 .map_err(|e| {
1680 AssetLoadError::AssetLoaderError(AssetLoaderError {
1681 path: asset_path.clone_owned(),
1682 loader_name: loader.type_path(),
1683 error: e.into(),
1684 })
1685 })
1686 }
1687
1688 pub async fn wait_for_asset<A: Asset>(
1696 &self,
1697 handle: &Handle<A>,
1700 ) -> Result<(), WaitForAssetError> {
1701 self.wait_for_asset_id(handle.id().untyped()).await
1702 }
1703
1704 pub async fn wait_for_asset_untyped(
1712 &self,
1713 handle: &UntypedHandle,
1716 ) -> Result<(), WaitForAssetError> {
1717 self.wait_for_asset_id(handle.id()).await
1718 }
1719
1720 pub async fn wait_for_asset_id(
1740 &self,
1741 id: impl Into<UntypedAssetId>,
1742 ) -> Result<(), WaitForAssetError> {
1743 let Ok(index) = id.into().try_into() else {
1744 return Err(WaitForAssetError::NotLoaded);
1746 };
1747 core::future::poll_fn(move |cx| self.wait_for_asset_id_poll_fn(cx, index)).await
1748 }
1749
1750 fn wait_for_asset_id_poll_fn(
1752 &self,
1753 cx: &mut core::task::Context<'_>,
1754 index: ErasedAssetIndex,
1755 ) -> Poll<Result<(), WaitForAssetError>> {
1756 let infos = self.read_infos();
1757
1758 let Some(info) = infos.get(index) else {
1759 return Poll::Ready(Err(WaitForAssetError::NotLoaded));
1760 };
1761
1762 match (&info.load_state, &info.rec_dep_load_state) {
1763 (LoadState::Loaded, RecursiveDependencyLoadState::Loaded) => Poll::Ready(Ok(())),
1764 (LoadState::NotLoaded, _) => Poll::Ready(Err(WaitForAssetError::NotLoaded)),
1766 (LoadState::Loading, _)
1768 | (_, RecursiveDependencyLoadState::Loading)
1769 | (LoadState::Loaded, RecursiveDependencyLoadState::NotLoaded) => {
1770 let has_waker = info
1772 .waiting_tasks
1773 .iter()
1774 .any(|waker| waker.will_wake(cx.waker()));
1775
1776 if has_waker {
1777 return Poll::Pending;
1778 }
1779
1780 let mut infos = {
1781 drop(infos);
1783 self.write_infos()
1784 };
1785
1786 let Some(info) = infos.get_mut(index) else {
1787 return Poll::Ready(Err(WaitForAssetError::NotLoaded));
1788 };
1789
1790 let is_loading = matches!(
1793 (&info.load_state, &info.rec_dep_load_state),
1794 (LoadState::Loading, _)
1795 | (_, RecursiveDependencyLoadState::Loading)
1796 | (LoadState::Loaded, RecursiveDependencyLoadState::NotLoaded)
1797 );
1798
1799 if !is_loading {
1800 cx.waker().wake_by_ref();
1801 } else {
1802 info.waiting_tasks.push(cx.waker().clone());
1804 }
1805
1806 Poll::Pending
1807 }
1808 (LoadState::Failed(error), _) => {
1809 Poll::Ready(Err(WaitForAssetError::Failed(error.clone())))
1810 }
1811 (_, RecursiveDependencyLoadState::Failed(error)) => {
1812 Poll::Ready(Err(WaitForAssetError::DependencyFailed(error.clone())))
1813 }
1814 }
1815 }
1816
1817 pub async fn write_default_loader_meta_file_for_path(
1828 &self,
1829 path: impl Into<AssetPath<'_>>,
1830 ) -> Result<(), WriteDefaultMetaError> {
1831 let path = path.into();
1832 let loader = self.get_path_asset_loader(&path).await?;
1833
1834 let meta = loader.default_meta();
1835 let serialized_meta = meta.serialize();
1836
1837 let source = self.get_source(path.source())?;
1838
1839 let reader = source.reader();
1840 match reader.read_meta_bytes(path.path()).await {
1841 Ok(_) => return Err(WriteDefaultMetaError::MetaAlreadyExists),
1842 Err(AssetReaderError::NotFound(_)) => {
1843 }
1845 Err(AssetReaderError::Io(err)) => {
1846 return Err(WriteDefaultMetaError::IoErrorFromExistingMetaCheck(err))
1847 }
1848 Err(AssetReaderError::HttpError(err)) => {
1849 return Err(WriteDefaultMetaError::HttpErrorFromExistingMetaCheck(err))
1850 }
1851 }
1852
1853 let writer = source.writer()?;
1854 writer
1855 .write_meta_bytes(path.path(), &serialized_meta)
1856 .await?;
1857
1858 Ok(())
1859 }
1860}
1861
1862pub struct LoadBuilder<'a> {
1874 asset_server: &'a AssetServer,
1876 meta_transform: Option<MetaTransform>,
1879 override_unapproved: bool,
1881 guard: Option<Box<dyn Send + Sync + 'static>>,
1883}
1884
1885impl<'a> LoadBuilder<'a> {
1886 #[must_use = "the load doesn't start until LoadBuilder has been consumed"]
1888 fn new(asset_server: &'a AssetServer) -> Self {
1889 Self {
1890 asset_server,
1891 meta_transform: None,
1892 override_unapproved: false,
1893 guard: None,
1894 }
1895 }
1896
1897 #[must_use = "the load doesn't start until LoadBuilder has been consumed"]
1905 pub fn with_settings<S: Settings>(
1906 mut self,
1907 settings: impl Fn(&mut S) + Send + Sync + 'static,
1908 ) -> Self {
1909 let new_transform = loader_settings_meta_transform(settings);
1910 if let Some(prev_transform) = self.meta_transform.take() {
1911 self.meta_transform = Some(Box::new(move |meta| {
1912 prev_transform(meta);
1913 new_transform(meta);
1914 }));
1915 } else {
1916 self.meta_transform = Some(new_transform);
1917 }
1918 self
1919 }
1920
1921 #[must_use = "the load doesn't start until LoadBuilder has been consumed"]
1925 pub fn override_unapproved(mut self) -> Self {
1926 self.override_unapproved = true;
1927 self
1928 }
1929
1930 #[must_use = "the load doesn't start until LoadBuilder has been consumed"]
1938 pub fn with_guard(mut self, guard: impl Send + Sync + 'static) -> Self {
1939 if self.guard.is_some() {
1940 warn!("Adding a second guard to a LoadBuilder drops the first guard! This is likely a mistake.");
1941 }
1942 self.guard = Some(Box::new(guard));
1945 self
1946 }
1947
1948 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
1958 pub fn load<'b, A: Asset>(self, asset_path: impl Into<AssetPath<'b>>) -> Handle<A> {
1959 self.load_typed_internal(TypeId::of::<A>(), Some(type_name::<A>()), asset_path.into())
1960 .typed_unchecked()
1961 }
1962
1963 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
1966 pub fn load_erased<'b>(
1967 self,
1968 type_id: TypeId,
1969 asset_path: impl Into<AssetPath<'b>>,
1970 ) -> UntypedHandle {
1971 self.load_typed_internal(type_id, None, asset_path.into())
1972 }
1973
1974 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
1998 pub fn load_untyped<'b>(
1999 self,
2000 asset_path: impl Into<AssetPath<'b>>,
2001 ) -> Handle<LoadedUntypedAsset> {
2002 self.asset_server.load_unknown_type_with_meta_transform(
2003 asset_path,
2004 self.meta_transform,
2005 self.guard,
2006 self.override_unapproved,
2007 )
2008 }
2009
2010 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
2019 pub async fn load_untyped_async<'b>(
2020 self,
2021 asset_path: impl Into<AssetPath<'b>>,
2022 ) -> Result<UntypedHandle, AssetLoadError> {
2023 let path: AssetPath = asset_path.into();
2024 if path.path() == Path::new("") {
2025 return Err(AssetLoadError::EmptyPath(path.into_owned()));
2026 }
2027
2028 self.asset_server.write_infos().stats.started_load_tasks += 1;
2029
2030 self.asset_server
2031 .load_internal(None, path, false, None)
2032 .await
2033 .map(|h| h.expect("handle must be returned, since we didn't pass in an input handle"))
2034 }
2035
2036 #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
2038 fn load_typed_internal(
2039 self,
2040 type_id: TypeId,
2041 type_name: Option<&str>,
2042 asset_path: AssetPath<'_>,
2043 ) -> UntypedHandle {
2044 self.asset_server.load_with_meta_transform(
2045 asset_path,
2046 type_id,
2047 type_name,
2048 self.meta_transform,
2049 self.guard,
2050 self.override_unapproved,
2051 )
2052 }
2053}
2054
2055pub fn handle_internal_asset_events(world: &mut World) {
2057 world.resource_scope(|world, server: Mut<AssetServer>| {
2058 let mut infos = server.write_infos();
2059 let var_name = vec![];
2060 let mut untyped_failures = var_name;
2061 for event in server.data.asset_event_receiver.try_iter() {
2062 match event {
2063 InternalAssetEvent::Loaded {
2064 index,
2065 loaded_asset,
2066 } => {
2067 infos.process_asset_load(
2068 index,
2069 loaded_asset,
2070 world,
2071 &server.data.asset_event_sender,
2072 );
2073 }
2074 InternalAssetEvent::LoadedWithDependencies { index } => {
2075 let sender = infos
2076 .dependency_loaded_event_sender
2077 .get(&index.type_id)
2078 .expect("Asset event sender should exist");
2079 sender(world, index.index);
2080 if let Some(info) = infos.get_mut(index) {
2081 for waker in info.waiting_tasks.drain(..) {
2082 waker.wake();
2083 }
2084 }
2085 }
2086 InternalAssetEvent::Failed { index, path, error } => {
2087 infos.process_asset_fail(index, error.clone());
2088
2089 untyped_failures.push(UntypedAssetLoadFailedEvent {
2091 id: index.into(),
2092 path: path.clone(),
2093 error: error.clone(),
2094 });
2095
2096 let sender = infos
2098 .dependency_failed_event_sender
2099 .get(&index.type_id)
2100 .expect("Asset failed event sender should exist");
2101 sender(world, index.index, path, error);
2102 }
2103 }
2104 }
2105
2106 if !untyped_failures.is_empty() {
2107 world.write_message_batch(untyped_failures);
2108 }
2109
2110 if !infos.watching_for_changes {
2113 return;
2114 }
2115
2116 fn queue_ancestors(
2117 asset_path: &AssetPath,
2118 infos: &AssetInfos,
2119 paths_to_reload: &mut HashSet<AssetPath<'static>>,
2120 ) {
2121 if let Some(dependents) = infos.loader_dependents.get(asset_path) {
2122 for dependent in dependents {
2123 paths_to_reload.insert(dependent.to_owned());
2124 queue_ancestors(dependent, infos, paths_to_reload);
2125 }
2126 }
2127 }
2128
2129 let mut folders_to_reload = Vec::default();
2130 let mut reload_parent_folders =
2131 |path: &PathBuf, source: &AssetSourceId<'static>, infos: &mut AssetInfos| {
2132 let mut new_loads = 0;
2133 for parent in path.ancestors().skip(1) {
2134 let parent_asset_path =
2135 AssetPath::from(parent.to_path_buf()).with_source(source.clone());
2136 for folder_handle in infos.get_path_handles(&parent_asset_path) {
2137 info!(
2138 "Reloading folder {parent_asset_path} because the content has changed"
2139 );
2140 new_loads += 1;
2141 folders_to_reload.push((folder_handle, parent_asset_path.clone()));
2142 }
2143 }
2144 infos.stats.started_load_tasks += new_loads;
2145 };
2146
2147 let mut paths_to_reload = <HashSet<_>>::default();
2148 let mut reload_path =
2149 |path: PathBuf, source: &AssetSourceId<'static>, infos: &AssetInfos| {
2150 let path = AssetPath::from(path).with_source(source);
2151 queue_ancestors(&path, infos, &mut paths_to_reload);
2152 paths_to_reload.insert(path);
2153 };
2154
2155 let mut handle_event = |source: AssetSourceId<'static>, event: AssetSourceEvent| {
2156 match event {
2157 AssetSourceEvent::AddedAsset(path) => {
2158 reload_parent_folders(&path, &source, &mut infos);
2159 reload_path(path, &source, &infos);
2160 }
2161 AssetSourceEvent::ModifiedAsset(path) | AssetSourceEvent::ModifiedMeta(path) => {
2164 reload_path(path, &source, &infos);
2165 }
2166 AssetSourceEvent::RenamedFolder { old, new } => {
2167 reload_parent_folders(&old, &source, &mut infos);
2168 reload_parent_folders(&new, &source, &mut infos);
2169 }
2170 AssetSourceEvent::RemovedAsset(path)
2171 | AssetSourceEvent::RemovedFolder(path)
2172 | AssetSourceEvent::AddedFolder(path) => {
2173 reload_parent_folders(&path, &source, &mut infos);
2174 }
2175 _ => {}
2176 }
2177 };
2178
2179 for source in server.data.sources.iter() {
2180 match server.data.mode {
2181 AssetServerMode::Unprocessed => {
2182 if let Some(receiver) = source.event_receiver() {
2183 while let Ok(event) = receiver.try_recv() {
2184 handle_event(source.id(), event);
2185 }
2186 }
2187 }
2188 AssetServerMode::Processed => {
2189 if let Some(receiver) = source.processed_event_receiver() {
2190 while let Ok(event) = receiver.try_recv() {
2191 handle_event(source.id(), event);
2192 }
2193 }
2194 }
2195 }
2196 }
2197
2198 #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
2201 drop(infos);
2202
2203 for (handle, path) in folders_to_reload {
2204 let index = (&handle).try_into().unwrap();
2206 server.load_folder_internal(index, path);
2207 }
2208 for path in paths_to_reload {
2209 server.reload_internal(path, true);
2210 }
2211
2212 #[cfg(not(any(target_arch = "wasm32", not(feature = "multi_threaded"))))]
2213 infos
2214 .pending_tasks
2215 .retain(|_, load_task| !load_task.is_finished());
2216 });
2217}
2218
2219pub fn publish_asset_server_diagnostics(
2221 asset_server: Res<AssetServer>,
2222 mut diagnostics: Diagnostics,
2223) {
2224 let infos = asset_server.read_infos();
2225 diagnostics.add_measurement(&AssetServer::STARTED_LOAD_COUNT, || {
2226 infos.stats.started_load_tasks as _
2227 });
2228}
2229
2230pub(crate) enum InternalAssetEvent {
2232 Loaded {
2233 index: ErasedAssetIndex,
2234 loaded_asset: ErasedLoadedAsset,
2235 },
2236 LoadedWithDependencies {
2237 index: ErasedAssetIndex,
2238 },
2239 Failed {
2240 index: ErasedAssetIndex,
2241 path: AssetPath<'static>,
2242 error: AssetLoadError,
2243 },
2244}
2245
2246#[derive(Component, Clone, Debug)]
2248pub enum LoadState {
2249 NotLoaded,
2251
2252 Loading,
2254
2255 Loaded,
2257
2258 Failed(Arc<AssetLoadError>),
2262}
2263
2264impl LoadState {
2265 pub fn is_loading(&self) -> bool {
2267 matches!(self, Self::Loading)
2268 }
2269
2270 pub fn is_loaded(&self) -> bool {
2272 matches!(self, Self::Loaded)
2273 }
2274
2275 pub fn is_failed(&self) -> bool {
2277 matches!(self, Self::Failed(_))
2278 }
2279}
2280
2281#[derive(Component, Clone, Debug)]
2283pub enum DependencyLoadState {
2284 NotLoaded,
2286
2287 Loading,
2289
2290 Loaded,
2292
2293 Failed(Arc<AssetLoadError>),
2297}
2298
2299impl DependencyLoadState {
2300 pub fn is_loading(&self) -> bool {
2302 matches!(self, Self::Loading)
2303 }
2304
2305 pub fn is_loaded(&self) -> bool {
2307 matches!(self, Self::Loaded)
2308 }
2309
2310 pub fn is_failed(&self) -> bool {
2312 matches!(self, Self::Failed(_))
2313 }
2314}
2315
2316#[derive(Component, Clone, Debug)]
2318pub enum RecursiveDependencyLoadState {
2319 NotLoaded,
2321
2322 Loading,
2324
2325 Loaded,
2327
2328 Failed(Arc<AssetLoadError>),
2333}
2334
2335impl RecursiveDependencyLoadState {
2336 pub fn is_loading(&self) -> bool {
2338 matches!(self, Self::Loading)
2339 }
2340
2341 pub fn is_loaded(&self) -> bool {
2343 matches!(self, Self::Loaded)
2344 }
2345
2346 pub fn is_failed(&self) -> bool {
2348 matches!(self, Self::Failed(_))
2349 }
2350}
2351
2352#[derive(Error, Debug, Clone)]
2354#[expect(
2355 missing_docs,
2356 reason = "Adding docs to the variants would not add information beyond the error message and the names"
2357)]
2358pub enum AssetLoadError {
2359 #[error("Attempted to load an asset with an empty path \"{0}\"")]
2360 EmptyPath(AssetPath<'static>),
2361 #[error("Requested handle of type {requested:?} for asset '{path}' does not match actual asset type '{actual_asset_name}', which used loader '{loader_name}'")]
2362 RequestedHandleTypeMismatch {
2363 path: AssetPath<'static>,
2364 requested: TypeId,
2365 actual_asset_name: &'static str,
2366 loader_name: &'static str,
2367 },
2368 #[error("Could not find an asset loader matching: Asset Type: {asset_type_id:?}; Path: {asset_path:?};")]
2369 MissingAssetLoader {
2370 asset_type_id: Option<TypeId>,
2371 asset_path: String,
2372 },
2373 #[error(transparent)]
2374 MissingAssetLoaderForExtension(#[from] MissingAssetLoaderForExtensionError),
2375 #[error(transparent)]
2376 MissingAssetLoaderForTypeName(#[from] MissingAssetLoaderForTypeNameError),
2377 #[error(transparent)]
2378 MissingAssetLoaderForTypeIdError(#[from] MissingAssetLoaderForTypeIdError),
2379 #[error(transparent)]
2380 AssetReaderError(#[from] AssetReaderError),
2381 #[error(transparent)]
2382 MissingAssetSourceError(#[from] MissingAssetSourceError),
2383 #[error(transparent)]
2384 MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError),
2385 #[error("Encountered an error while reading asset metadata bytes")]
2386 AssetMetaReadError,
2387 #[error("Failed to deserialize meta for asset {path}: {error}")]
2388 DeserializeMeta {
2389 path: AssetPath<'static>,
2390 error: Box<DeserializeMetaError>,
2391 },
2392 #[error("Asset '{path}' is configured to be processed. It cannot be loaded directly.")]
2393 #[from(ignore)]
2394 CannotLoadProcessedAsset { path: AssetPath<'static> },
2395 #[error("Asset '{path}' is configured to be ignored. It cannot be loaded.")]
2396 #[from(ignore)]
2397 CannotLoadIgnoredAsset { path: AssetPath<'static> },
2398 #[error("Failed to load asset '{path}', asset loader '{loader_name}' panicked")]
2399 AssetLoaderPanic {
2400 path: AssetPath<'static>,
2401 loader_name: &'static str,
2402 },
2403 #[error(transparent)]
2404 AssetLoaderError(#[from] AssetLoaderError),
2405 #[error(transparent)]
2406 AddAsyncError(#[from] AddAsyncError),
2407 #[error("The file at '{}' does not contain the labeled asset '{}'; it contains the following {} assets: {}",
2408 base_path,
2409 label,
2410 all_labels.len(),
2411 all_labels.iter().map(|l| format!("'{l}'")).collect::<Vec<_>>().join(", "))]
2412 MissingLabel {
2413 base_path: AssetPath<'static>,
2414 label: String,
2415 all_labels: Vec<String>,
2416 },
2417}
2418
2419#[derive(Error, Debug, Clone)]
2421#[error("Failed to load asset '{path}' with asset loader '{loader_name}': {error}")]
2422pub struct AssetLoaderError {
2423 path: AssetPath<'static>,
2424 loader_name: &'static str,
2425 error: Arc<BevyError>,
2426}
2427
2428impl AssetLoaderError {
2429 pub fn path(&self) -> &AssetPath<'static> {
2431 &self.path
2432 }
2433
2434 pub fn error(&self) -> &BevyError {
2439 &self.error
2440 }
2441}
2442
2443#[derive(Error, Debug, Clone)]
2445#[error("An error occurred while resolving an asset added by `add_async`: {error}")]
2446pub struct AddAsyncError {
2447 error: Arc<dyn core::error::Error + Send + Sync + 'static>,
2448}
2449
2450#[derive(Error, Debug, Clone, PartialEq, Eq)]
2452#[error("no `AssetLoader` found{}", format_missing_asset_ext(extensions))]
2453pub struct MissingAssetLoaderForExtensionError {
2454 extensions: Vec<String>,
2455}
2456
2457#[derive(Error, Debug, Clone, PartialEq, Eq)]
2459#[error("no `AssetLoader` found with the name '{type_name}'")]
2460pub struct MissingAssetLoaderForTypeNameError {
2461 pub type_name: String,
2463}
2464
2465#[derive(Error, Debug, Clone, PartialEq, Eq)]
2467#[error("no `AssetLoader` found with the ID '{type_id:?}'")]
2468pub struct MissingAssetLoaderForTypeIdError {
2469 pub type_id: TypeId,
2471}
2472
2473fn format_missing_asset_ext(exts: &[String]) -> String {
2474 if !exts.is_empty() {
2475 format!(
2476 " for the following extension{}: {}",
2477 if exts.len() > 1 { "s" } else { "" },
2478 exts.join(", ")
2479 )
2480 } else {
2481 " for file with no extension".to_string()
2482 }
2483}
2484
2485impl core::fmt::Debug for AssetServer {
2486 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2487 f.debug_struct("AssetServer")
2488 .field("info", &self.data.infos.read())
2489 .finish()
2490 }
2491}
2492
2493const UNTYPED_SOURCE_SUFFIX: &str = "--untyped";
2496
2497#[derive(Error, Debug, Clone)]
2499pub enum WaitForAssetError {
2500 #[error("tried to wait for an asset that is not being loaded")]
2502 NotLoaded,
2503 #[error(transparent)]
2505 Failed(Arc<AssetLoadError>),
2506 #[error(transparent)]
2508 DependencyFailed(Arc<AssetLoadError>),
2509}
2510
2511#[derive(Error, Debug)]
2512pub enum WriteDefaultMetaError {
2513 #[error(transparent)]
2514 MissingAssetLoader(#[from] MissingAssetLoaderForExtensionError),
2515 #[error(transparent)]
2516 MissingAssetSource(#[from] MissingAssetSourceError),
2517 #[error(transparent)]
2518 MissingAssetWriter(#[from] MissingAssetWriterError),
2519 #[error("failed to write default asset meta file: {0}")]
2520 FailedToWriteMeta(#[from] AssetWriterError),
2521 #[error("asset meta file already exists, so avoiding overwrite")]
2522 MetaAlreadyExists,
2523 #[error("encountered an I/O error while reading the existing meta file: {0}")]
2524 IoErrorFromExistingMetaCheck(Arc<std::io::Error>),
2525 #[error("encountered HTTP status {0} when reading the existing meta file")]
2526 HttpErrorFromExistingMetaCheck(u16),
2527}