bevy_asset/io/
source.rs

1use crate::{
2    io::{processor_gated::ProcessorGatedReader, AssetSourceEvent, AssetWatcher},
3    processor::ProcessingState,
4};
5use alloc::{
6    boxed::Box,
7    string::{String, ToString},
8    sync::Arc,
9};
10use atomicow::CowArc;
11use bevy_ecs::resource::Resource;
12use bevy_platform::collections::HashMap;
13use core::{fmt::Display, hash::Hash, time::Duration};
14use thiserror::Error;
15use tracing::warn;
16
17use super::{ErasedAssetReader, ErasedAssetWriter};
18
19/// A reference to an "asset source", which maps to an [`AssetReader`](crate::io::AssetReader) and/or [`AssetWriter`](crate::io::AssetWriter).
20///
21/// * [`AssetSourceId::Default`] corresponds to "default asset paths" that don't specify a source: `/path/to/asset.png`
22/// * [`AssetSourceId::Name`] corresponds to asset paths that _do_ specify a source: `remote://path/to/asset.png`, where `remote` is the name.
23#[derive(Default, Clone, Debug, Eq)]
24pub enum AssetSourceId<'a> {
25    /// The default asset source.
26    #[default]
27    Default,
28    /// A non-default named asset source.
29    Name(CowArc<'a, str>),
30}
31
32impl<'a> Display for AssetSourceId<'a> {
33    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
34        match self.as_str() {
35            None => write!(f, "AssetSourceId::Default"),
36            Some(v) => write!(f, "AssetSourceId::Name({v})"),
37        }
38    }
39}
40
41impl<'a> AssetSourceId<'a> {
42    /// Creates a new [`AssetSourceId`]
43    pub fn new(source: Option<impl Into<CowArc<'a, str>>>) -> AssetSourceId<'a> {
44        match source {
45            Some(source) => AssetSourceId::Name(source.into()),
46            None => AssetSourceId::Default,
47        }
48    }
49
50    /// Returns [`None`] if this is [`AssetSourceId::Default`] and [`Some`] containing the
51    /// name if this is [`AssetSourceId::Name`].
52    pub fn as_str(&self) -> Option<&str> {
53        match self {
54            AssetSourceId::Default => None,
55            AssetSourceId::Name(v) => Some(v),
56        }
57    }
58
59    /// If this is not already an owned / static id, create one. Otherwise, it will return itself (with a static lifetime).
60    pub fn into_owned(self) -> AssetSourceId<'static> {
61        match self {
62            AssetSourceId::Default => AssetSourceId::Default,
63            AssetSourceId::Name(v) => AssetSourceId::Name(v.into_owned()),
64        }
65    }
66
67    /// Clones into an owned [`AssetSourceId<'static>`].
68    /// This is equivalent to `.clone().into_owned()`.
69    #[inline]
70    pub fn clone_owned(&self) -> AssetSourceId<'static> {
71        self.clone().into_owned()
72    }
73}
74
75// This is only implemented for static lifetimes to ensure `Path::clone` does not allocate
76// by ensuring that this is stored as a `CowArc::Static`.
77// Please read https://github.com/bevyengine/bevy/issues/19844 before changing this!
78impl From<&'static str> for AssetSourceId<'static> {
79    fn from(value: &'static str) -> Self {
80        AssetSourceId::Name(value.into())
81    }
82}
83
84impl<'a, 'b> From<&'a AssetSourceId<'b>> for AssetSourceId<'b> {
85    fn from(value: &'a AssetSourceId<'b>) -> Self {
86        value.clone()
87    }
88}
89
90impl From<Option<&'static str>> for AssetSourceId<'static> {
91    fn from(value: Option<&'static str>) -> Self {
92        match value {
93            Some(value) => AssetSourceId::Name(value.into()),
94            None => AssetSourceId::Default,
95        }
96    }
97}
98
99impl From<String> for AssetSourceId<'static> {
100    fn from(value: String) -> Self {
101        AssetSourceId::Name(value.into())
102    }
103}
104
105impl<'a> Hash for AssetSourceId<'a> {
106    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
107        self.as_str().hash(state);
108    }
109}
110
111impl<'a> PartialEq for AssetSourceId<'a> {
112    fn eq(&self, other: &Self) -> bool {
113        self.as_str().eq(&other.as_str())
114    }
115}
116
117/// Metadata about an "asset source", such as how to construct the [`AssetReader`](crate::io::AssetReader) and [`AssetWriter`](crate::io::AssetWriter) for the source,
118/// and whether or not the source is processed.
119pub struct AssetSourceBuilder {
120    /// The [`ErasedAssetReader`] to use on the unprocessed asset.
121    pub reader: Box<dyn FnMut() -> Box<dyn ErasedAssetReader> + Send + Sync>,
122    /// The [`ErasedAssetWriter`] to use on the unprocessed asset.
123    pub writer: Option<Box<dyn FnMut(bool) -> Option<Box<dyn ErasedAssetWriter>> + Send + Sync>>,
124    /// The [`AssetWatcher`] to use for unprocessed assets, if any.
125    pub watcher: Option<
126        Box<
127            dyn FnMut(async_channel::Sender<AssetSourceEvent>) -> Option<Box<dyn AssetWatcher>>
128                + Send
129                + Sync,
130        >,
131    >,
132    /// The [`ErasedAssetReader`] to use for processed assets.
133    pub processed_reader: Option<Box<dyn FnMut() -> Box<dyn ErasedAssetReader> + Send + Sync>>,
134    /// The [`ErasedAssetWriter`] to use for processed assets.
135    pub processed_writer:
136        Option<Box<dyn FnMut(bool) -> Option<Box<dyn ErasedAssetWriter>> + Send + Sync>>,
137    /// The [`AssetWatcher`] to use for processed assets, if any.
138    pub processed_watcher: Option<
139        Box<
140            dyn FnMut(async_channel::Sender<AssetSourceEvent>) -> Option<Box<dyn AssetWatcher>>
141                + Send
142                + Sync,
143        >,
144    >,
145    /// The warning message to display when watching an unprocessed asset fails.
146    pub watch_warning: Option<&'static str>,
147    /// The warning message to display when watching a processed asset fails.
148    pub processed_watch_warning: Option<&'static str>,
149}
150
151impl AssetSourceBuilder {
152    /// Creates a new builder, starting with the provided reader.
153    pub fn new(
154        reader: impl FnMut() -> Box<dyn ErasedAssetReader> + Send + Sync + 'static,
155    ) -> AssetSourceBuilder {
156        Self {
157            reader: Box::new(reader),
158            writer: None,
159            watcher: None,
160            processed_reader: None,
161            processed_writer: None,
162            processed_watcher: None,
163            watch_warning: None,
164            processed_watch_warning: None,
165        }
166    }
167
168    /// Builds a new [`AssetSource`] with the given `id`. If `watch` is true, the unprocessed source will watch for changes.
169    /// If `watch_processed` is true, the processed source will watch for changes.
170    pub fn build(
171        &mut self,
172        id: AssetSourceId<'static>,
173        watch: bool,
174        watch_processed: bool,
175    ) -> AssetSource {
176        let reader = self.reader.as_mut()();
177        let writer = self.writer.as_mut().and_then(|w| w(false));
178        let processed_writer = self.processed_writer.as_mut().and_then(|w| w(true));
179        let mut source = AssetSource {
180            id: id.clone(),
181            reader,
182            writer,
183            processed_reader: self
184                .processed_reader
185                .as_mut()
186                .map(|r| r())
187                .map(Into::<Arc<_>>::into),
188            ungated_processed_reader: None,
189            processed_writer,
190            event_receiver: None,
191            watcher: None,
192            processed_event_receiver: None,
193            processed_watcher: None,
194        };
195
196        if watch {
197            let (sender, receiver) = async_channel::unbounded();
198            match self.watcher.as_mut().and_then(|w| w(sender)) {
199                Some(w) => {
200                    source.watcher = Some(w);
201                    source.event_receiver = Some(receiver);
202                }
203                None => {
204                    if let Some(warning) = self.watch_warning {
205                        warn!("{id} does not have an AssetWatcher configured. {warning}");
206                    }
207                }
208            }
209        }
210
211        if watch_processed {
212            let (sender, receiver) = async_channel::unbounded();
213            match self.processed_watcher.as_mut().and_then(|w| w(sender)) {
214                Some(w) => {
215                    source.processed_watcher = Some(w);
216                    source.processed_event_receiver = Some(receiver);
217                }
218                None => {
219                    if let Some(warning) = self.processed_watch_warning {
220                        warn!("{id} does not have a processed AssetWatcher configured. {warning}");
221                    }
222                }
223            }
224        }
225        source
226    }
227
228    /// Will use the given `reader` function to construct unprocessed [`AssetReader`](crate::io::AssetReader) instances.
229    pub fn with_reader(
230        mut self,
231        reader: impl FnMut() -> Box<dyn ErasedAssetReader> + Send + Sync + 'static,
232    ) -> Self {
233        self.reader = Box::new(reader);
234        self
235    }
236
237    /// Will use the given `writer` function to construct unprocessed [`AssetWriter`](crate::io::AssetWriter) instances.
238    pub fn with_writer(
239        mut self,
240        writer: impl FnMut(bool) -> Option<Box<dyn ErasedAssetWriter>> + Send + Sync + 'static,
241    ) -> Self {
242        self.writer = Some(Box::new(writer));
243        self
244    }
245
246    /// Will use the given `watcher` function to construct unprocessed [`AssetWatcher`] instances.
247    pub fn with_watcher(
248        mut self,
249        watcher: impl FnMut(async_channel::Sender<AssetSourceEvent>) -> Option<Box<dyn AssetWatcher>>
250            + Send
251            + Sync
252            + 'static,
253    ) -> Self {
254        self.watcher = Some(Box::new(watcher));
255        self
256    }
257
258    /// Will use the given `reader` function to construct processed [`AssetReader`](crate::io::AssetReader) instances.
259    pub fn with_processed_reader(
260        mut self,
261        reader: impl FnMut() -> Box<dyn ErasedAssetReader> + Send + Sync + 'static,
262    ) -> Self {
263        self.processed_reader = Some(Box::new(reader));
264        self
265    }
266
267    /// Will use the given `writer` function to construct processed [`AssetWriter`](crate::io::AssetWriter) instances.
268    pub fn with_processed_writer(
269        mut self,
270        writer: impl FnMut(bool) -> Option<Box<dyn ErasedAssetWriter>> + Send + Sync + 'static,
271    ) -> Self {
272        self.processed_writer = Some(Box::new(writer));
273        self
274    }
275
276    /// Will use the given `watcher` function to construct processed [`AssetWatcher`] instances.
277    pub fn with_processed_watcher(
278        mut self,
279        watcher: impl FnMut(async_channel::Sender<AssetSourceEvent>) -> Option<Box<dyn AssetWatcher>>
280            + Send
281            + Sync
282            + 'static,
283    ) -> Self {
284        self.processed_watcher = Some(Box::new(watcher));
285        self
286    }
287
288    /// Enables a warning for the unprocessed source watcher, which will print when watching is enabled and the unprocessed source doesn't have a watcher.
289    pub fn with_watch_warning(mut self, warning: &'static str) -> Self {
290        self.watch_warning = Some(warning);
291        self
292    }
293
294    /// Enables a warning for the processed source watcher, which will print when watching is enabled and the processed source doesn't have a watcher.
295    pub fn with_processed_watch_warning(mut self, warning: &'static str) -> Self {
296        self.processed_watch_warning = Some(warning);
297        self
298    }
299
300    /// Returns a builder containing the "platform default source" for the given `path` and `processed_path`.
301    /// For most platforms, this will use [`FileAssetReader`](crate::io::file::FileAssetReader) / [`FileAssetWriter`](crate::io::file::FileAssetWriter),
302    /// but some platforms (such as Android) have their own default readers / writers / watchers.
303    pub fn platform_default(path: &str, processed_path: Option<&str>) -> Self {
304        let default = Self::new(AssetSource::get_default_reader(path.to_string()))
305            .with_writer(AssetSource::get_default_writer(path.to_string()))
306            .with_watcher(AssetSource::get_default_watcher(
307                path.to_string(),
308                Duration::from_millis(300),
309            ))
310            .with_watch_warning(AssetSource::get_default_watch_warning());
311        if let Some(processed_path) = processed_path {
312            default
313                .with_processed_reader(AssetSource::get_default_reader(processed_path.to_string()))
314                .with_processed_writer(AssetSource::get_default_writer(processed_path.to_string()))
315                .with_processed_watcher(AssetSource::get_default_watcher(
316                    processed_path.to_string(),
317                    Duration::from_millis(300),
318                ))
319                .with_processed_watch_warning(AssetSource::get_default_watch_warning())
320        } else {
321            default
322        }
323    }
324}
325
326/// A [`Resource`] that hold (repeatable) functions capable of producing new [`AssetReader`](crate::io::AssetReader) and [`AssetWriter`](crate::io::AssetWriter) instances
327/// for a given asset source.
328#[derive(Resource, Default)]
329pub struct AssetSourceBuilders {
330    sources: HashMap<CowArc<'static, str>, AssetSourceBuilder>,
331    default: Option<AssetSourceBuilder>,
332}
333
334impl AssetSourceBuilders {
335    /// Inserts a new builder with the given `id`
336    pub fn insert(&mut self, id: impl Into<AssetSourceId<'static>>, source: AssetSourceBuilder) {
337        match id.into() {
338            AssetSourceId::Default => {
339                self.default = Some(source);
340            }
341            AssetSourceId::Name(name) => {
342                self.sources.insert(name, source);
343            }
344        }
345    }
346
347    /// Gets a mutable builder with the given `id`, if it exists.
348    pub fn get_mut<'a, 'b>(
349        &'a mut self,
350        id: impl Into<AssetSourceId<'b>>,
351    ) -> Option<&'a mut AssetSourceBuilder> {
352        match id.into() {
353            AssetSourceId::Default => self.default.as_mut(),
354            AssetSourceId::Name(name) => self.sources.get_mut(&name.into_owned()),
355        }
356    }
357
358    /// Builds a new [`AssetSources`] collection. If `watch` is true, the unprocessed sources will watch for changes.
359    /// If `watch_processed` is true, the processed sources will watch for changes.
360    pub fn build_sources(&mut self, watch: bool, watch_processed: bool) -> AssetSources {
361        let mut sources = <HashMap<_, _>>::default();
362        for (id, source) in &mut self.sources {
363            let source = source.build(
364                AssetSourceId::Name(id.clone_owned()),
365                watch,
366                watch_processed,
367            );
368            sources.insert(id.clone_owned(), source);
369        }
370
371        AssetSources {
372            sources,
373            default: self
374                .default
375                .as_mut()
376                .map(|p| p.build(AssetSourceId::Default, watch, watch_processed))
377                .expect(MISSING_DEFAULT_SOURCE),
378        }
379    }
380
381    /// Initializes the default [`AssetSourceBuilder`] if it has not already been set.
382    pub fn init_default_source(&mut self, path: &str, processed_path: Option<&str>) {
383        self.default
384            .get_or_insert_with(|| AssetSourceBuilder::platform_default(path, processed_path));
385    }
386}
387
388/// A collection of unprocessed and processed [`AssetReader`](crate::io::AssetReader), [`AssetWriter`](crate::io::AssetWriter), and [`AssetWatcher`] instances
389/// for a specific asset source, identified by an [`AssetSourceId`].
390pub struct AssetSource {
391    id: AssetSourceId<'static>,
392    reader: Box<dyn ErasedAssetReader>,
393    writer: Option<Box<dyn ErasedAssetWriter>>,
394    processed_reader: Option<Arc<dyn ErasedAssetReader>>,
395    /// The ungated version of `processed_reader`.
396    ///
397    /// This allows the processor to read all the processed assets to initialize itself without
398    /// being gated on itself (causing a deadlock).
399    ungated_processed_reader: Option<Arc<dyn ErasedAssetReader>>,
400    processed_writer: Option<Box<dyn ErasedAssetWriter>>,
401    watcher: Option<Box<dyn AssetWatcher>>,
402    processed_watcher: Option<Box<dyn AssetWatcher>>,
403    event_receiver: Option<async_channel::Receiver<AssetSourceEvent>>,
404    processed_event_receiver: Option<async_channel::Receiver<AssetSourceEvent>>,
405}
406
407impl AssetSource {
408    /// Returns this source's id.
409    #[inline]
410    pub fn id(&self) -> AssetSourceId<'static> {
411        self.id.clone()
412    }
413
414    /// Return's this source's unprocessed [`AssetReader`](crate::io::AssetReader).
415    #[inline]
416    pub fn reader(&self) -> &dyn ErasedAssetReader {
417        &*self.reader
418    }
419
420    /// Return's this source's unprocessed [`AssetWriter`](crate::io::AssetWriter), if it exists.
421    #[inline]
422    pub fn writer(&self) -> Result<&dyn ErasedAssetWriter, MissingAssetWriterError> {
423        self.writer
424            .as_deref()
425            .ok_or_else(|| MissingAssetWriterError(self.id.clone_owned()))
426    }
427
428    /// Return's this source's processed [`AssetReader`](crate::io::AssetReader), if it exists.
429    #[inline]
430    pub fn processed_reader(
431        &self,
432    ) -> Result<&dyn ErasedAssetReader, MissingProcessedAssetReaderError> {
433        self.processed_reader
434            .as_deref()
435            .ok_or_else(|| MissingProcessedAssetReaderError(self.id.clone_owned()))
436    }
437
438    /// Return's this source's ungated processed [`AssetReader`](crate::io::AssetReader), if it
439    /// exists.
440    #[inline]
441    pub(crate) fn ungated_processed_reader(&self) -> Option<&dyn ErasedAssetReader> {
442        self.ungated_processed_reader.as_deref()
443    }
444
445    /// Return's this source's processed [`AssetWriter`](crate::io::AssetWriter), if it exists.
446    #[inline]
447    pub fn processed_writer(
448        &self,
449    ) -> Result<&dyn ErasedAssetWriter, MissingProcessedAssetWriterError> {
450        self.processed_writer
451            .as_deref()
452            .ok_or_else(|| MissingProcessedAssetWriterError(self.id.clone_owned()))
453    }
454
455    /// Return's this source's unprocessed event receiver, if the source is currently watching for changes.
456    #[inline]
457    pub fn event_receiver(&self) -> Option<&async_channel::Receiver<AssetSourceEvent>> {
458        self.event_receiver.as_ref()
459    }
460
461    /// Return's this source's processed event receiver, if the source is currently watching for changes.
462    #[inline]
463    pub fn processed_event_receiver(&self) -> Option<&async_channel::Receiver<AssetSourceEvent>> {
464        self.processed_event_receiver.as_ref()
465    }
466
467    /// Returns true if the assets in this source should be processed.
468    #[inline]
469    pub fn should_process(&self) -> bool {
470        self.processed_writer.is_some()
471    }
472
473    /// Returns a builder function for this platform's default [`AssetReader`](crate::io::AssetReader). `path` is the relative path to
474    /// the asset root.
475    pub fn get_default_reader(
476        _path: String,
477    ) -> impl FnMut() -> Box<dyn ErasedAssetReader> + Send + Sync {
478        move || {
479            #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
480            return Box::new(super::file::FileAssetReader::new(&_path));
481            #[cfg(target_arch = "wasm32")]
482            return Box::new(super::wasm::HttpWasmAssetReader::new(&_path));
483            #[cfg(target_os = "android")]
484            return Box::new(super::android::AndroidAssetReader);
485        }
486    }
487
488    /// Returns a builder function for this platform's default [`AssetWriter`](crate::io::AssetWriter). `path` is the relative path to
489    /// the asset root. This will return [`None`] if this platform does not support writing assets by default.
490    pub fn get_default_writer(
491        _path: String,
492    ) -> impl FnMut(bool) -> Option<Box<dyn ErasedAssetWriter>> + Send + Sync {
493        move |_create_root: bool| {
494            #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
495            return Some(Box::new(super::file::FileAssetWriter::new(
496                &_path,
497                _create_root,
498            )));
499            #[cfg(any(target_arch = "wasm32", target_os = "android"))]
500            return None;
501        }
502    }
503
504    /// Returns the default non-existent [`AssetWatcher`] warning for the current platform.
505    pub fn get_default_watch_warning() -> &'static str {
506        #[cfg(target_arch = "wasm32")]
507        return "Web does not currently support watching assets.";
508        #[cfg(target_os = "android")]
509        return "Android does not currently support watching assets.";
510        #[cfg(all(
511            not(target_arch = "wasm32"),
512            not(target_os = "android"),
513            not(feature = "file_watcher")
514        ))]
515        return "Consider enabling the `file_watcher` feature.";
516        #[cfg(all(
517            not(target_arch = "wasm32"),
518            not(target_os = "android"),
519            feature = "file_watcher"
520        ))]
521        return "Consider adding an \"assets\" directory.";
522    }
523
524    /// Returns a builder function for this platform's default [`AssetWatcher`]. `path` is the relative path to
525    /// the asset root. This will return [`None`] if this platform does not support watching assets by default.
526    /// `file_debounce_time` is the amount of time to wait (and debounce duplicate events) before returning an event.
527    /// Higher durations reduce duplicates but increase the amount of time before a change event is processed. If the
528    /// duration is set too low, some systems might surface events _before_ their filesystem has the changes.
529    #[cfg_attr(
530        any(
531            not(feature = "file_watcher"),
532            target_arch = "wasm32",
533            target_os = "android"
534        ),
535        expect(
536            unused_variables,
537            reason = "The `path` and `file_debounce_wait_time` arguments are unused when on WASM, Android, or if the `file_watcher` feature is disabled."
538        )
539    )]
540    pub fn get_default_watcher(
541        path: String,
542        file_debounce_wait_time: Duration,
543    ) -> impl FnMut(async_channel::Sender<AssetSourceEvent>) -> Option<Box<dyn AssetWatcher>> + Send + Sync
544    {
545        move |sender: async_channel::Sender<AssetSourceEvent>| {
546            #[cfg(all(
547                feature = "file_watcher",
548                not(target_arch = "wasm32"),
549                not(target_os = "android")
550            ))]
551            {
552                let path = super::file::get_base_path().join(path.clone());
553                if path.exists() {
554                    Some(Box::new(
555                        super::file::FileWatcher::new(
556                            path.clone(),
557                            sender,
558                            file_debounce_wait_time,
559                        )
560                        .unwrap_or_else(|e| {
561                            panic!("Failed to create file watcher from path {path:?}, {e:?}")
562                        }),
563                    ))
564                } else {
565                    warn!("Skip creating file watcher because path {path:?} does not exist.");
566                    None
567                }
568            }
569            #[cfg(any(
570                not(feature = "file_watcher"),
571                target_arch = "wasm32",
572                target_os = "android"
573            ))]
574            return None;
575        }
576    }
577
578    /// This will cause processed [`AssetReader`](crate::io::AssetReader) futures (such as [`AssetReader::read`](crate::io::AssetReader::read)) to wait until
579    /// the [`AssetProcessor`](crate::AssetProcessor) has finished processing the requested asset.
580    pub(crate) fn gate_on_processor(&mut self, processing_state: Arc<ProcessingState>) {
581        if let Some(reader) = self.processed_reader.take() {
582            self.ungated_processed_reader = Some(reader.clone());
583            self.processed_reader = Some(Arc::new(ProcessorGatedReader::new(
584                self.id(),
585                reader,
586                processing_state,
587            )));
588        }
589    }
590}
591
592/// A collection of [`AssetSource`]s.
593pub struct AssetSources {
594    sources: HashMap<CowArc<'static, str>, AssetSource>,
595    default: AssetSource,
596}
597
598impl AssetSources {
599    /// Gets the [`AssetSource`] with the given `id`, if it exists.
600    pub fn get<'a, 'b>(
601        &'a self,
602        id: impl Into<AssetSourceId<'b>>,
603    ) -> Result<&'a AssetSource, MissingAssetSourceError> {
604        match id.into().into_owned() {
605            AssetSourceId::Default => Ok(&self.default),
606            AssetSourceId::Name(name) => self
607                .sources
608                .get(&name)
609                .ok_or(MissingAssetSourceError(AssetSourceId::Name(name))),
610        }
611    }
612
613    /// Iterates all asset sources in the collection (including the default source).
614    pub fn iter(&self) -> impl Iterator<Item = &AssetSource> {
615        self.sources.values().chain(Some(&self.default))
616    }
617
618    /// Mutably iterates all asset sources in the collection (including the default source).
619    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut AssetSource> {
620        self.sources.values_mut().chain(Some(&mut self.default))
621    }
622
623    /// Iterates all processed asset sources in the collection (including the default source).
624    pub fn iter_processed(&self) -> impl Iterator<Item = &AssetSource> {
625        self.iter().filter(|p| p.should_process())
626    }
627
628    /// Mutably iterates all processed asset sources in the collection (including the default source).
629    pub fn iter_processed_mut(&mut self) -> impl Iterator<Item = &mut AssetSource> {
630        self.iter_mut().filter(|p| p.should_process())
631    }
632
633    /// Iterates over the [`AssetSourceId`] of every [`AssetSource`] in the collection (including the default source).
634    pub fn ids(&self) -> impl Iterator<Item = AssetSourceId<'static>> + '_ {
635        self.sources
636            .keys()
637            .map(|k| AssetSourceId::Name(k.clone_owned()))
638            .chain(Some(AssetSourceId::Default))
639    }
640
641    /// This will cause processed [`AssetReader`](crate::io::AssetReader) futures (such as [`AssetReader::read`](crate::io::AssetReader::read)) to wait until
642    /// the [`AssetProcessor`](crate::AssetProcessor) has finished processing the requested asset.
643    pub(crate) fn gate_on_processor(&mut self, processing_state: Arc<ProcessingState>) {
644        for source in self.iter_processed_mut() {
645            source.gate_on_processor(processing_state.clone());
646        }
647    }
648}
649
650/// An error returned when an [`AssetSource`] does not exist for a given id.
651#[derive(Error, Debug, Clone, PartialEq, Eq)]
652#[error("Asset Source '{0}' does not exist")]
653pub struct MissingAssetSourceError(AssetSourceId<'static>);
654
655/// An error returned when an [`AssetWriter`](crate::io::AssetWriter) does not exist for a given id.
656#[derive(Error, Debug, Clone)]
657#[error("Asset Source '{0}' does not have an AssetWriter.")]
658pub struct MissingAssetWriterError(AssetSourceId<'static>);
659
660/// An error returned when a processed [`AssetReader`](crate::io::AssetReader) does not exist for a given id.
661#[derive(Error, Debug, Clone, PartialEq, Eq)]
662#[error("Asset Source '{0}' does not have a processed AssetReader.")]
663pub struct MissingProcessedAssetReaderError(AssetSourceId<'static>);
664
665/// An error returned when a processed [`AssetWriter`](crate::io::AssetWriter) does not exist for a given id.
666#[derive(Error, Debug, Clone)]
667#[error("Asset Source '{0}' does not have a processed AssetWriter.")]
668pub struct MissingProcessedAssetWriterError(AssetSourceId<'static>);
669
670const MISSING_DEFAULT_SOURCE: &str =
671    "A default AssetSource is required. Add one to `AssetSourceBuilders`";