1use crate::{
2 io::{processor_gated::ProcessorGatedReader, AssetSourceEvent, AssetWatcher},
3 processor::AssetProcessorData,
4};
5use bevy_ecs::system::Resource;
6use bevy_utils::tracing::{error, warn};
7use bevy_utils::{CowArc, Duration, HashMap};
8use std::{fmt::Display, hash::Hash, sync::Arc};
9use thiserror::Error;
10
11use super::{ErasedAssetReader, ErasedAssetWriter};
12
13#[allow(unused_imports)]
15use crate::io::{AssetReader, AssetWriter};
16
17#[derive(Default, Clone, Debug, Eq)]
22pub enum AssetSourceId<'a> {
23 #[default]
25 Default,
26 Name(CowArc<'a, str>),
28}
29
30impl<'a> Display for AssetSourceId<'a> {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::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 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 pub fn as_str(&self) -> Option<&str> {
51 match self {
52 AssetSourceId::Default => None,
53 AssetSourceId::Name(v) => Some(v),
54 }
55 }
56
57 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 #[inline]
68 pub fn clone_owned(&self) -> AssetSourceId<'static> {
69 self.clone().into_owned()
70 }
71}
72
73impl From<&'static str> for AssetSourceId<'static> {
74 fn from(value: &'static str) -> Self {
75 AssetSourceId::Name(value.into())
76 }
77}
78
79impl<'a, 'b> From<&'a AssetSourceId<'b>> for AssetSourceId<'b> {
80 fn from(value: &'a AssetSourceId<'b>) -> Self {
81 value.clone()
82 }
83}
84
85impl From<Option<&'static str>> for AssetSourceId<'static> {
86 fn from(value: Option<&'static str>) -> Self {
87 match value {
88 Some(value) => AssetSourceId::Name(value.into()),
89 None => AssetSourceId::Default,
90 }
91 }
92}
93
94impl From<String> for AssetSourceId<'static> {
95 fn from(value: String) -> Self {
96 AssetSourceId::Name(value.into())
97 }
98}
99
100impl<'a> Hash for AssetSourceId<'a> {
101 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
102 self.as_str().hash(state);
103 }
104}
105
106impl<'a> PartialEq for AssetSourceId<'a> {
107 fn eq(&self, other: &Self) -> bool {
108 self.as_str().eq(&other.as_str())
109 }
110}
111
112#[derive(Default)]
115pub struct AssetSourceBuilder {
116 pub reader: Option<Box<dyn FnMut() -> Box<dyn ErasedAssetReader> + Send + Sync>>,
117 pub writer: Option<Box<dyn FnMut(bool) -> Option<Box<dyn ErasedAssetWriter>> + Send + Sync>>,
118 pub watcher: Option<
119 Box<
120 dyn FnMut(crossbeam_channel::Sender<AssetSourceEvent>) -> Option<Box<dyn AssetWatcher>>
121 + Send
122 + Sync,
123 >,
124 >,
125 pub processed_reader: Option<Box<dyn FnMut() -> Box<dyn ErasedAssetReader> + Send + Sync>>,
126 pub processed_writer:
127 Option<Box<dyn FnMut(bool) -> Option<Box<dyn ErasedAssetWriter>> + Send + Sync>>,
128 pub processed_watcher: Option<
129 Box<
130 dyn FnMut(crossbeam_channel::Sender<AssetSourceEvent>) -> Option<Box<dyn AssetWatcher>>
131 + Send
132 + Sync,
133 >,
134 >,
135 pub watch_warning: Option<&'static str>,
136 pub processed_watch_warning: Option<&'static str>,
137}
138
139impl AssetSourceBuilder {
140 pub fn build(
143 &mut self,
144 id: AssetSourceId<'static>,
145 watch: bool,
146 watch_processed: bool,
147 ) -> Option<AssetSource> {
148 let reader = self.reader.as_mut()?();
149 let writer = self.writer.as_mut().and_then(|w| w(false));
150 let processed_writer = self.processed_writer.as_mut().and_then(|w| w(true));
151 let mut source = AssetSource {
152 id: id.clone(),
153 reader,
154 writer,
155 processed_reader: self.processed_reader.as_mut().map(|r| r()),
156 processed_writer,
157 event_receiver: None,
158 watcher: None,
159 processed_event_receiver: None,
160 processed_watcher: None,
161 };
162
163 if watch {
164 let (sender, receiver) = crossbeam_channel::unbounded();
165 match self.watcher.as_mut().and_then(|w| w(sender)) {
166 Some(w) => {
167 source.watcher = Some(w);
168 source.event_receiver = Some(receiver);
169 }
170 None => {
171 if let Some(warning) = self.watch_warning {
172 warn!("{id} does not have an AssetWatcher configured. {warning}");
173 }
174 }
175 }
176 }
177
178 if watch_processed {
179 let (sender, receiver) = crossbeam_channel::unbounded();
180 match self.processed_watcher.as_mut().and_then(|w| w(sender)) {
181 Some(w) => {
182 source.processed_watcher = Some(w);
183 source.processed_event_receiver = Some(receiver);
184 }
185 None => {
186 if let Some(warning) = self.processed_watch_warning {
187 warn!("{id} does not have a processed AssetWatcher configured. {warning}");
188 }
189 }
190 }
191 }
192 Some(source)
193 }
194
195 pub fn with_reader(
197 mut self,
198 reader: impl FnMut() -> Box<dyn ErasedAssetReader> + Send + Sync + 'static,
199 ) -> Self {
200 self.reader = Some(Box::new(reader));
201 self
202 }
203
204 pub fn with_writer(
206 mut self,
207 writer: impl FnMut(bool) -> Option<Box<dyn ErasedAssetWriter>> + Send + Sync + 'static,
208 ) -> Self {
209 self.writer = Some(Box::new(writer));
210 self
211 }
212
213 pub fn with_watcher(
215 mut self,
216 watcher: impl FnMut(crossbeam_channel::Sender<AssetSourceEvent>) -> Option<Box<dyn AssetWatcher>>
217 + Send
218 + Sync
219 + 'static,
220 ) -> Self {
221 self.watcher = Some(Box::new(watcher));
222 self
223 }
224
225 pub fn with_processed_reader(
227 mut self,
228 reader: impl FnMut() -> Box<dyn ErasedAssetReader> + Send + Sync + 'static,
229 ) -> Self {
230 self.processed_reader = Some(Box::new(reader));
231 self
232 }
233
234 pub fn with_processed_writer(
236 mut self,
237 writer: impl FnMut(bool) -> Option<Box<dyn ErasedAssetWriter>> + Send + Sync + 'static,
238 ) -> Self {
239 self.processed_writer = Some(Box::new(writer));
240 self
241 }
242
243 pub fn with_processed_watcher(
245 mut self,
246 watcher: impl FnMut(crossbeam_channel::Sender<AssetSourceEvent>) -> Option<Box<dyn AssetWatcher>>
247 + Send
248 + Sync
249 + 'static,
250 ) -> Self {
251 self.processed_watcher = Some(Box::new(watcher));
252 self
253 }
254
255 pub fn with_watch_warning(mut self, warning: &'static str) -> Self {
257 self.watch_warning = Some(warning);
258 self
259 }
260
261 pub fn with_processed_watch_warning(mut self, warning: &'static str) -> Self {
263 self.processed_watch_warning = Some(warning);
264 self
265 }
266
267 pub fn platform_default(path: &str, processed_path: Option<&str>) -> Self {
271 let default = Self::default()
272 .with_reader(AssetSource::get_default_reader(path.to_string()))
273 .with_writer(AssetSource::get_default_writer(path.to_string()))
274 .with_watcher(AssetSource::get_default_watcher(
275 path.to_string(),
276 Duration::from_millis(300),
277 ))
278 .with_watch_warning(AssetSource::get_default_watch_warning());
279 if let Some(processed_path) = processed_path {
280 default
281 .with_processed_reader(AssetSource::get_default_reader(processed_path.to_string()))
282 .with_processed_writer(AssetSource::get_default_writer(processed_path.to_string()))
283 .with_processed_watcher(AssetSource::get_default_watcher(
284 processed_path.to_string(),
285 Duration::from_millis(300),
286 ))
287 .with_processed_watch_warning(AssetSource::get_default_watch_warning())
288 } else {
289 default
290 }
291 }
292}
293
294#[derive(Resource, Default)]
297pub struct AssetSourceBuilders {
298 sources: HashMap<CowArc<'static, str>, AssetSourceBuilder>,
299 default: Option<AssetSourceBuilder>,
300}
301
302impl AssetSourceBuilders {
303 pub fn insert(&mut self, id: impl Into<AssetSourceId<'static>>, source: AssetSourceBuilder) {
305 match id.into() {
306 AssetSourceId::Default => {
307 self.default = Some(source);
308 }
309 AssetSourceId::Name(name) => {
310 self.sources.insert(name, source);
311 }
312 }
313 }
314
315 pub fn get_mut<'a, 'b>(
317 &'a mut self,
318 id: impl Into<AssetSourceId<'b>>,
319 ) -> Option<&'a mut AssetSourceBuilder> {
320 match id.into() {
321 AssetSourceId::Default => self.default.as_mut(),
322 AssetSourceId::Name(name) => self.sources.get_mut(&name.into_owned()),
323 }
324 }
325
326 pub fn build_sources(&mut self, watch: bool, watch_processed: bool) -> AssetSources {
329 let mut sources = HashMap::new();
330 for (id, source) in &mut self.sources {
331 if let Some(data) = source.build(
332 AssetSourceId::Name(id.clone_owned()),
333 watch,
334 watch_processed,
335 ) {
336 sources.insert(id.clone_owned(), data);
337 }
338 }
339
340 AssetSources {
341 sources,
342 default: self
343 .default
344 .as_mut()
345 .and_then(|p| p.build(AssetSourceId::Default, watch, watch_processed))
346 .expect(MISSING_DEFAULT_SOURCE),
347 }
348 }
349
350 pub fn init_default_source(&mut self, path: &str, processed_path: Option<&str>) {
352 self.default
353 .get_or_insert_with(|| AssetSourceBuilder::platform_default(path, processed_path));
354 }
355}
356
357pub struct AssetSource {
360 id: AssetSourceId<'static>,
361 reader: Box<dyn ErasedAssetReader>,
362 writer: Option<Box<dyn ErasedAssetWriter>>,
363 processed_reader: Option<Box<dyn ErasedAssetReader>>,
364 processed_writer: Option<Box<dyn ErasedAssetWriter>>,
365 watcher: Option<Box<dyn AssetWatcher>>,
366 processed_watcher: Option<Box<dyn AssetWatcher>>,
367 event_receiver: Option<crossbeam_channel::Receiver<AssetSourceEvent>>,
368 processed_event_receiver: Option<crossbeam_channel::Receiver<AssetSourceEvent>>,
369}
370
371impl AssetSource {
372 pub fn build() -> AssetSourceBuilder {
374 AssetSourceBuilder::default()
375 }
376
377 #[inline]
379 pub fn id(&self) -> AssetSourceId<'static> {
380 self.id.clone()
381 }
382
383 #[inline]
385 pub fn reader(&self) -> &dyn ErasedAssetReader {
386 &*self.reader
387 }
388
389 #[inline]
391 pub fn writer(&self) -> Result<&dyn ErasedAssetWriter, MissingAssetWriterError> {
392 self.writer
393 .as_deref()
394 .ok_or_else(|| MissingAssetWriterError(self.id.clone_owned()))
395 }
396
397 #[inline]
399 pub fn processed_reader(
400 &self,
401 ) -> Result<&dyn ErasedAssetReader, MissingProcessedAssetReaderError> {
402 self.processed_reader
403 .as_deref()
404 .ok_or_else(|| MissingProcessedAssetReaderError(self.id.clone_owned()))
405 }
406
407 #[inline]
409 pub fn processed_writer(
410 &self,
411 ) -> Result<&dyn ErasedAssetWriter, MissingProcessedAssetWriterError> {
412 self.processed_writer
413 .as_deref()
414 .ok_or_else(|| MissingProcessedAssetWriterError(self.id.clone_owned()))
415 }
416
417 #[inline]
419 pub fn event_receiver(&self) -> Option<&crossbeam_channel::Receiver<AssetSourceEvent>> {
420 self.event_receiver.as_ref()
421 }
422
423 #[inline]
425 pub fn processed_event_receiver(
426 &self,
427 ) -> Option<&crossbeam_channel::Receiver<AssetSourceEvent>> {
428 self.processed_event_receiver.as_ref()
429 }
430
431 #[inline]
433 pub fn should_process(&self) -> bool {
434 self.processed_writer.is_some()
435 }
436
437 pub fn get_default_reader(
440 _path: String,
441 ) -> impl FnMut() -> Box<dyn ErasedAssetReader> + Send + Sync {
442 move || {
443 #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
444 return Box::new(super::file::FileAssetReader::new(&_path));
445 #[cfg(target_arch = "wasm32")]
446 return Box::new(super::wasm::HttpWasmAssetReader::new(&_path));
447 #[cfg(target_os = "android")]
448 return Box::new(super::android::AndroidAssetReader);
449 }
450 }
451
452 pub fn get_default_writer(
455 _path: String,
456 ) -> impl FnMut(bool) -> Option<Box<dyn ErasedAssetWriter>> + Send + Sync {
457 move |_create_root: bool| {
458 #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
459 return Some(Box::new(super::file::FileAssetWriter::new(
460 &_path,
461 _create_root,
462 )));
463 #[cfg(any(target_arch = "wasm32", target_os = "android"))]
464 return None;
465 }
466 }
467
468 pub fn get_default_watch_warning() -> &'static str {
470 #[cfg(target_arch = "wasm32")]
471 return "Web does not currently support watching assets.";
472 #[cfg(target_os = "android")]
473 return "Android does not currently support watching assets.";
474 #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
475 return "Consider enabling the `file_watcher` feature.";
476 }
477
478 #[allow(unused)]
484 pub fn get_default_watcher(
485 path: String,
486 file_debounce_wait_time: Duration,
487 ) -> impl FnMut(crossbeam_channel::Sender<AssetSourceEvent>) -> Option<Box<dyn AssetWatcher>>
488 + Send
489 + Sync {
490 move |sender: crossbeam_channel::Sender<AssetSourceEvent>| {
491 #[cfg(all(
492 feature = "file_watcher",
493 not(target_arch = "wasm32"),
494 not(target_os = "android")
495 ))]
496 return Some(Box::new(
497 super::file::FileWatcher::new(
498 std::path::PathBuf::from(path.clone()),
499 sender,
500 file_debounce_wait_time,
501 )
502 .unwrap_or_else(|e| {
503 panic!("Failed to create file watcher from path {path:?}, {e:?}")
504 }),
505 ));
506 #[cfg(any(
507 not(feature = "file_watcher"),
508 target_arch = "wasm32",
509 target_os = "android"
510 ))]
511 return None;
512 }
513 }
514
515 pub fn gate_on_processor(&mut self, processor_data: Arc<AssetProcessorData>) {
518 if let Some(reader) = self.processed_reader.take() {
519 self.processed_reader = Some(Box::new(ProcessorGatedReader::new(
520 self.id(),
521 reader,
522 processor_data,
523 )));
524 }
525 }
526}
527
528pub struct AssetSources {
530 sources: HashMap<CowArc<'static, str>, AssetSource>,
531 default: AssetSource,
532}
533
534impl AssetSources {
535 pub fn get<'a, 'b>(
537 &'a self,
538 id: impl Into<AssetSourceId<'b>>,
539 ) -> Result<&'a AssetSource, MissingAssetSourceError> {
540 match id.into().into_owned() {
541 AssetSourceId::Default => Ok(&self.default),
542 AssetSourceId::Name(name) => self
543 .sources
544 .get(&name)
545 .ok_or_else(|| MissingAssetSourceError(AssetSourceId::Name(name))),
546 }
547 }
548
549 pub fn iter(&self) -> impl Iterator<Item = &AssetSource> {
551 self.sources.values().chain(Some(&self.default))
552 }
553
554 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut AssetSource> {
556 self.sources.values_mut().chain(Some(&mut self.default))
557 }
558
559 pub fn iter_processed(&self) -> impl Iterator<Item = &AssetSource> {
561 self.iter().filter(|p| p.should_process())
562 }
563
564 pub fn iter_processed_mut(&mut self) -> impl Iterator<Item = &mut AssetSource> {
566 self.iter_mut().filter(|p| p.should_process())
567 }
568
569 pub fn ids(&self) -> impl Iterator<Item = AssetSourceId<'static>> + '_ {
571 self.sources
572 .keys()
573 .map(|k| AssetSourceId::Name(k.clone_owned()))
574 .chain(Some(AssetSourceId::Default))
575 }
576
577 pub fn gate_on_processor(&mut self, processor_data: Arc<AssetProcessorData>) {
580 for source in self.iter_processed_mut() {
581 source.gate_on_processor(processor_data.clone());
582 }
583 }
584}
585
586#[derive(Error, Debug, Clone, PartialEq, Eq)]
588#[error("Asset Source '{0}' does not exist")]
589pub struct MissingAssetSourceError(AssetSourceId<'static>);
590
591#[derive(Error, Debug, Clone)]
593#[error("Asset Source '{0}' does not have an AssetWriter.")]
594pub struct MissingAssetWriterError(AssetSourceId<'static>);
595
596#[derive(Error, Debug, Clone, PartialEq, Eq)]
598#[error("Asset Source '{0}' does not have a processed AssetReader.")]
599pub struct MissingProcessedAssetReaderError(AssetSourceId<'static>);
600
601#[derive(Error, Debug, Clone)]
603#[error("Asset Source '{0}' does not have a processed AssetWriter.")]
604pub struct MissingProcessedAssetWriterError(AssetSourceId<'static>);
605
606const MISSING_DEFAULT_SOURCE: &str =
607 "A default AssetSource is required. Add one to `AssetSourceBuilders`";