bevy_asset/io/
source.rs

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