1#[cfg(all(feature = "file_watcher", target_arch = "wasm32"))]
2compile_error!(
3 "The \"file_watcher\" feature for hot reloading does not work \
4 on Wasm.\nDisable \"file_watcher\" \
5 when compiling to Wasm"
6);
7
8#[cfg(target_os = "android")]
9pub mod android;
10pub mod embedded;
11#[cfg(not(target_arch = "wasm32"))]
12pub mod file;
13pub mod gated;
14pub mod memory;
15pub mod processor_gated;
16#[cfg(target_arch = "wasm32")]
17pub mod wasm;
18
19mod source;
20
21pub use futures_lite::AsyncWriteExt;
22pub use source::*;
23
24use alloc::{boxed::Box, sync::Arc, vec::Vec};
25use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
26use core::future::Future;
27use core::{
28 mem::size_of,
29 pin::Pin,
30 task::{Context, Poll},
31};
32use futures_io::{AsyncRead, AsyncWrite};
33use futures_lite::{ready, Stream};
34use std::path::{Path, PathBuf};
35use thiserror::Error;
36
37#[derive(Error, Debug, Clone)]
39pub enum AssetReaderError {
40 #[error("Path not found: {}", _0.display())]
42 NotFound(PathBuf),
43
44 #[error("Encountered an I/O error while loading asset: {0}")]
46 Io(Arc<std::io::Error>),
47
48 #[error("Encountered HTTP status {0:?} when loading asset")]
51 HttpError(u16),
52}
53
54impl PartialEq for AssetReaderError {
55 #[inline]
57 fn eq(&self, other: &Self) -> bool {
58 match (self, other) {
59 (Self::NotFound(path), Self::NotFound(other_path)) => path == other_path,
60 (Self::Io(error), Self::Io(other_error)) => error.kind() == other_error.kind(),
61 (Self::HttpError(code), Self::HttpError(other_code)) => code == other_code,
62 _ => false,
63 }
64 }
65}
66
67impl Eq for AssetReaderError {}
68
69impl From<std::io::Error> for AssetReaderError {
70 fn from(value: std::io::Error) -> Self {
71 Self::Io(Arc::new(value))
72 }
73}
74
75pub const STACK_FUTURE_SIZE: usize = 10 * size_of::<&()>();
81
82pub use stackfuture::StackFuture;
83
84pub trait AsyncSeekForward {
90 fn poll_seek_forward(
106 self: Pin<&mut Self>,
107 cx: &mut Context<'_>,
108 offset: u64,
109 ) -> Poll<futures_io::Result<u64>>;
110}
111
112impl<T: ?Sized + AsyncSeekForward + Unpin> AsyncSeekForward for Box<T> {
113 fn poll_seek_forward(
114 mut self: Pin<&mut Self>,
115 cx: &mut Context<'_>,
116 offset: u64,
117 ) -> Poll<futures_io::Result<u64>> {
118 Pin::new(&mut **self).poll_seek_forward(cx, offset)
119 }
120}
121
122pub trait AsyncSeekForwardExt: AsyncSeekForward {
124 fn seek_forward(&mut self, offset: u64) -> SeekForwardFuture<'_, Self>
126 where
127 Self: Unpin,
128 {
129 SeekForwardFuture {
130 seeker: self,
131 offset,
132 }
133 }
134}
135
136impl<R: AsyncSeekForward + ?Sized> AsyncSeekForwardExt for R {}
137
138#[derive(Debug)]
139#[must_use = "futures do nothing unless you `.await` or poll them"]
140pub struct SeekForwardFuture<'a, S: Unpin + ?Sized> {
141 seeker: &'a mut S,
142 offset: u64,
143}
144
145impl<S: Unpin + ?Sized> Unpin for SeekForwardFuture<'_, S> {}
146
147impl<S: AsyncSeekForward + Unpin + ?Sized> Future for SeekForwardFuture<'_, S> {
148 type Output = futures_lite::io::Result<u64>;
149
150 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
151 let offset = self.offset;
152 Pin::new(&mut *self.seeker).poll_seek_forward(cx, offset)
153 }
154}
155
156pub trait Reader: AsyncRead + AsyncSeekForward + Unpin + Send + Sync {
163 fn read_to_end<'a>(
170 &'a mut self,
171 buf: &'a mut Vec<u8>,
172 ) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
173 let future = futures_lite::AsyncReadExt::read_to_end(self, buf);
174 StackFuture::from(future)
175 }
176}
177
178impl Reader for Box<dyn Reader + '_> {
179 fn read_to_end<'a>(
180 &'a mut self,
181 buf: &'a mut Vec<u8>,
182 ) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
183 (**self).read_to_end(buf)
184 }
185}
186
187pub trait AssetReaderFuture:
189 ConditionalSendFuture<Output = Result<Self::Value, AssetReaderError>>
190{
191 type Value;
192}
193
194impl<F, T> AssetReaderFuture for F
195where
196 F: ConditionalSendFuture<Output = Result<T, AssetReaderError>>,
197{
198 type Value = T;
199}
200
201pub trait AssetReader: Send + Sync + 'static {
210 fn read<'a>(&'a self, path: &'a Path) -> impl AssetReaderFuture<Value: Reader + 'a>;
232 fn read_meta<'a>(&'a self, path: &'a Path) -> impl AssetReaderFuture<Value: Reader + 'a>;
234 fn read_directory<'a>(
236 &'a self,
237 path: &'a Path,
238 ) -> impl ConditionalSendFuture<Output = Result<Box<PathStream>, AssetReaderError>>;
239 fn is_directory<'a>(
241 &'a self,
242 path: &'a Path,
243 ) -> impl ConditionalSendFuture<Output = Result<bool, AssetReaderError>>;
244 fn read_meta_bytes<'a>(
247 &'a self,
248 path: &'a Path,
249 ) -> impl ConditionalSendFuture<Output = Result<Vec<u8>, AssetReaderError>> {
250 async {
251 let mut meta_reader = self.read_meta(path).await?;
252 let mut meta_bytes = Vec::new();
253 meta_reader.read_to_end(&mut meta_bytes).await?;
254 Ok(meta_bytes)
255 }
256 }
257}
258
259pub trait ErasedAssetReader: Send + Sync + 'static {
262 fn read<'a>(
264 &'a self,
265 path: &'a Path,
266 ) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>>;
267 fn read_meta<'a>(
269 &'a self,
270 path: &'a Path,
271 ) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>>;
272 fn read_directory<'a>(
274 &'a self,
275 path: &'a Path,
276 ) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>>;
277 fn is_directory<'a>(
279 &'a self,
280 path: &'a Path,
281 ) -> BoxedFuture<'a, Result<bool, AssetReaderError>>;
282 fn read_meta_bytes<'a>(
285 &'a self,
286 path: &'a Path,
287 ) -> BoxedFuture<'a, Result<Vec<u8>, AssetReaderError>>;
288}
289
290impl<T: AssetReader> ErasedAssetReader for T {
291 fn read<'a>(
292 &'a self,
293 path: &'a Path,
294 ) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>> {
295 Box::pin(async {
296 let reader = Self::read(self, path).await?;
297 Ok(Box::new(reader) as Box<dyn Reader>)
298 })
299 }
300 fn read_meta<'a>(
301 &'a self,
302 path: &'a Path,
303 ) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>> {
304 Box::pin(async {
305 let reader = Self::read_meta(self, path).await?;
306 Ok(Box::new(reader) as Box<dyn Reader>)
307 })
308 }
309 fn read_directory<'a>(
310 &'a self,
311 path: &'a Path,
312 ) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>> {
313 Box::pin(Self::read_directory(self, path))
314 }
315 fn is_directory<'a>(
316 &'a self,
317 path: &'a Path,
318 ) -> BoxedFuture<'a, Result<bool, AssetReaderError>> {
319 Box::pin(Self::is_directory(self, path))
320 }
321 fn read_meta_bytes<'a>(
322 &'a self,
323 path: &'a Path,
324 ) -> BoxedFuture<'a, Result<Vec<u8>, AssetReaderError>> {
325 Box::pin(Self::read_meta_bytes(self, path))
326 }
327}
328
329pub type Writer = dyn AsyncWrite + Unpin + Send + Sync;
330
331pub type PathStream = dyn Stream<Item = PathBuf> + Unpin + Send;
332
333#[derive(Error, Debug)]
335pub enum AssetWriterError {
336 #[error("encountered an io error while loading asset: {0}")]
338 Io(#[from] std::io::Error),
339}
340
341pub trait AssetWriter: Send + Sync + 'static {
350 fn write<'a>(
352 &'a self,
353 path: &'a Path,
354 ) -> impl ConditionalSendFuture<Output = Result<Box<Writer>, AssetWriterError>>;
355 fn write_meta<'a>(
358 &'a self,
359 path: &'a Path,
360 ) -> impl ConditionalSendFuture<Output = Result<Box<Writer>, AssetWriterError>>;
361 fn remove<'a>(
363 &'a self,
364 path: &'a Path,
365 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
366 fn remove_meta<'a>(
369 &'a self,
370 path: &'a Path,
371 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
372 fn rename<'a>(
374 &'a self,
375 old_path: &'a Path,
376 new_path: &'a Path,
377 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
378 fn rename_meta<'a>(
381 &'a self,
382 old_path: &'a Path,
383 new_path: &'a Path,
384 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
385 fn create_directory<'a>(
388 &'a self,
389 path: &'a Path,
390 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
391 fn remove_directory<'a>(
393 &'a self,
394 path: &'a Path,
395 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
396 fn remove_empty_directory<'a>(
399 &'a self,
400 path: &'a Path,
401 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
402 fn remove_assets_in_directory<'a>(
404 &'a self,
405 path: &'a Path,
406 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
407 fn write_bytes<'a>(
409 &'a self,
410 path: &'a Path,
411 bytes: &'a [u8],
412 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>> {
413 async {
414 let mut writer = self.write(path).await?;
415 writer.write_all(bytes).await?;
416 writer.flush().await?;
417 Ok(())
418 }
419 }
420 fn write_meta_bytes<'a>(
422 &'a self,
423 path: &'a Path,
424 bytes: &'a [u8],
425 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>> {
426 async {
427 let mut meta_writer = self.write_meta(path).await?;
428 meta_writer.write_all(bytes).await?;
429 meta_writer.flush().await?;
430 Ok(())
431 }
432 }
433}
434
435pub trait ErasedAssetWriter: Send + Sync + 'static {
438 fn write<'a>(
440 &'a self,
441 path: &'a Path,
442 ) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>>;
443 fn write_meta<'a>(
446 &'a self,
447 path: &'a Path,
448 ) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>>;
449 fn remove<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
451 fn remove_meta<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
454 fn rename<'a>(
456 &'a self,
457 old_path: &'a Path,
458 new_path: &'a Path,
459 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
460 fn rename_meta<'a>(
463 &'a self,
464 old_path: &'a Path,
465 new_path: &'a Path,
466 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
467 fn create_directory<'a>(
470 &'a self,
471 path: &'a Path,
472 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
473 fn remove_directory<'a>(
475 &'a self,
476 path: &'a Path,
477 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
478 fn remove_empty_directory<'a>(
481 &'a self,
482 path: &'a Path,
483 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
484 fn remove_assets_in_directory<'a>(
486 &'a self,
487 path: &'a Path,
488 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
489 fn write_bytes<'a>(
491 &'a self,
492 path: &'a Path,
493 bytes: &'a [u8],
494 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
495 fn write_meta_bytes<'a>(
497 &'a self,
498 path: &'a Path,
499 bytes: &'a [u8],
500 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
501}
502
503impl<T: AssetWriter> ErasedAssetWriter for T {
504 fn write<'a>(
505 &'a self,
506 path: &'a Path,
507 ) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>> {
508 Box::pin(Self::write(self, path))
509 }
510 fn write_meta<'a>(
511 &'a self,
512 path: &'a Path,
513 ) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>> {
514 Box::pin(Self::write_meta(self, path))
515 }
516 fn remove<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
517 Box::pin(Self::remove(self, path))
518 }
519 fn remove_meta<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
520 Box::pin(Self::remove_meta(self, path))
521 }
522 fn rename<'a>(
523 &'a self,
524 old_path: &'a Path,
525 new_path: &'a Path,
526 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
527 Box::pin(Self::rename(self, old_path, new_path))
528 }
529 fn rename_meta<'a>(
530 &'a self,
531 old_path: &'a Path,
532 new_path: &'a Path,
533 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
534 Box::pin(Self::rename_meta(self, old_path, new_path))
535 }
536 fn create_directory<'a>(
537 &'a self,
538 path: &'a Path,
539 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
540 Box::pin(Self::create_directory(self, path))
541 }
542 fn remove_directory<'a>(
543 &'a self,
544 path: &'a Path,
545 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
546 Box::pin(Self::remove_directory(self, path))
547 }
548 fn remove_empty_directory<'a>(
549 &'a self,
550 path: &'a Path,
551 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
552 Box::pin(Self::remove_empty_directory(self, path))
553 }
554 fn remove_assets_in_directory<'a>(
555 &'a self,
556 path: &'a Path,
557 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
558 Box::pin(Self::remove_assets_in_directory(self, path))
559 }
560 fn write_bytes<'a>(
561 &'a self,
562 path: &'a Path,
563 bytes: &'a [u8],
564 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
565 Box::pin(Self::write_bytes(self, path, bytes))
566 }
567 fn write_meta_bytes<'a>(
568 &'a self,
569 path: &'a Path,
570 bytes: &'a [u8],
571 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
572 Box::pin(Self::write_meta_bytes(self, path, bytes))
573 }
574}
575
576#[derive(Clone, Debug, PartialEq, Eq)]
578pub enum AssetSourceEvent {
579 AddedAsset(PathBuf),
581 ModifiedAsset(PathBuf),
583 RemovedAsset(PathBuf),
585 RenamedAsset { old: PathBuf, new: PathBuf },
587 AddedMeta(PathBuf),
589 ModifiedMeta(PathBuf),
591 RemovedMeta(PathBuf),
593 RenamedMeta { old: PathBuf, new: PathBuf },
595 AddedFolder(PathBuf),
597 RemovedFolder(PathBuf),
599 RenamedFolder { old: PathBuf, new: PathBuf },
601 RemovedUnknown {
605 path: PathBuf,
607 is_meta: bool,
611 },
612}
613
614pub trait AssetWatcher: Send + Sync + 'static {}
617
618pub struct VecReader {
620 bytes: Vec<u8>,
621 bytes_read: usize,
622}
623
624impl VecReader {
625 pub fn new(bytes: Vec<u8>) -> Self {
627 Self {
628 bytes_read: 0,
629 bytes,
630 }
631 }
632}
633
634impl AsyncRead for VecReader {
635 fn poll_read(
636 mut self: Pin<&mut Self>,
637 cx: &mut Context<'_>,
638 buf: &mut [u8],
639 ) -> Poll<futures_io::Result<usize>> {
640 if self.bytes_read >= self.bytes.len() {
641 Poll::Ready(Ok(0))
642 } else {
643 let n = ready!(Pin::new(&mut &self.bytes[self.bytes_read..]).poll_read(cx, buf))?;
644 self.bytes_read += n;
645 Poll::Ready(Ok(n))
646 }
647 }
648}
649
650impl AsyncSeekForward for VecReader {
651 fn poll_seek_forward(
652 mut self: Pin<&mut Self>,
653 _cx: &mut Context<'_>,
654 offset: u64,
655 ) -> Poll<std::io::Result<u64>> {
656 let result = self
657 .bytes_read
658 .try_into()
659 .map(|bytes_read: u64| bytes_read + offset);
660
661 if let Ok(new_pos) = result {
662 self.bytes_read = new_pos as _;
663 Poll::Ready(Ok(new_pos as _))
664 } else {
665 Poll::Ready(Err(std::io::Error::new(
666 std::io::ErrorKind::InvalidInput,
667 "seek position is out of range",
668 )))
669 }
670 }
671}
672
673impl Reader for VecReader {
674 fn read_to_end<'a>(
675 &'a mut self,
676 buf: &'a mut Vec<u8>,
677 ) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
678 StackFuture::from(async {
679 if self.bytes_read >= self.bytes.len() {
680 Ok(0)
681 } else {
682 buf.extend_from_slice(&self.bytes[self.bytes_read..]);
683 let n = self.bytes.len() - self.bytes_read;
684 self.bytes_read = self.bytes.len();
685 Ok(n)
686 }
687 })
688 }
689}
690
691pub struct SliceReader<'a> {
693 bytes: &'a [u8],
694 bytes_read: usize,
695}
696
697impl<'a> SliceReader<'a> {
698 pub fn new(bytes: &'a [u8]) -> Self {
700 Self {
701 bytes,
702 bytes_read: 0,
703 }
704 }
705}
706
707impl<'a> AsyncRead for SliceReader<'a> {
708 fn poll_read(
709 mut self: Pin<&mut Self>,
710 cx: &mut Context<'_>,
711 buf: &mut [u8],
712 ) -> Poll<std::io::Result<usize>> {
713 if self.bytes_read >= self.bytes.len() {
714 Poll::Ready(Ok(0))
715 } else {
716 let n = ready!(Pin::new(&mut &self.bytes[self.bytes_read..]).poll_read(cx, buf))?;
717 self.bytes_read += n;
718 Poll::Ready(Ok(n))
719 }
720 }
721}
722
723impl<'a> AsyncSeekForward for SliceReader<'a> {
724 fn poll_seek_forward(
725 mut self: Pin<&mut Self>,
726 _cx: &mut Context<'_>,
727 offset: u64,
728 ) -> Poll<std::io::Result<u64>> {
729 let result = self
730 .bytes_read
731 .try_into()
732 .map(|bytes_read: u64| bytes_read + offset);
733
734 if let Ok(new_pos) = result {
735 self.bytes_read = new_pos as _;
736
737 Poll::Ready(Ok(new_pos as _))
738 } else {
739 Poll::Ready(Err(std::io::Error::new(
740 std::io::ErrorKind::InvalidInput,
741 "seek position is out of range",
742 )))
743 }
744 }
745}
746
747impl Reader for SliceReader<'_> {
748 fn read_to_end<'a>(
749 &'a mut self,
750 buf: &'a mut Vec<u8>,
751 ) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
752 StackFuture::from(async {
753 if self.bytes_read >= self.bytes.len() {
754 Ok(0)
755 } else {
756 buf.extend_from_slice(&self.bytes[self.bytes_read..]);
757 let n = self.bytes.len() - self.bytes_read;
758 self.bytes_read = self.bytes.len();
759 Ok(n)
760 }
761 })
762 }
763}
764
765pub(crate) fn get_meta_path(path: &Path) -> PathBuf {
767 let mut meta_path = path.to_path_buf();
768 let mut extension = path.extension().unwrap_or_default().to_os_string();
769 extension.push(".meta");
770 meta_path.set_extension(extension);
771 meta_path
772}
773
774#[cfg(any(target_arch = "wasm32", target_os = "android"))]
775struct EmptyPathStream;
777
778#[cfg(any(target_arch = "wasm32", target_os = "android"))]
779impl Stream for EmptyPathStream {
780 type Item = PathBuf;
781
782 fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
783 Poll::Ready(None)
784 }
785}