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