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 memory;
14pub mod processor_gated;
15#[cfg(target_arch = "wasm32")]
16pub mod wasm;
17#[cfg(any(feature = "http", feature = "https"))]
18pub mod web;
19
20#[cfg(test)]
21pub mod gated;
22
23mod source;
24
25pub use futures_lite::AsyncWriteExt;
26pub use source::*;
27
28use alloc::{boxed::Box, sync::Arc, vec::Vec};
29use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
30use core::future::Future;
31use core::{
32 mem::size_of,
33 pin::Pin,
34 task::{Context, Poll},
35};
36use futures_io::{AsyncRead, AsyncWrite};
37use futures_lite::{ready, Stream};
38use std::path::{Path, PathBuf};
39use thiserror::Error;
40
41#[derive(Error, Debug, Clone)]
43pub enum AssetReaderError {
44 #[error("Path not found: {}", _0.display())]
46 NotFound(PathBuf),
47
48 #[error("Encountered an I/O error while loading asset: {0}")]
50 Io(Arc<std::io::Error>),
51
52 #[error("Encountered HTTP status {0:?} when loading asset")]
56 HttpError(u16),
57}
58
59impl PartialEq for AssetReaderError {
60 #[inline]
62 fn eq(&self, other: &Self) -> bool {
63 match (self, other) {
64 (Self::NotFound(path), Self::NotFound(other_path)) => path == other_path,
65 (Self::Io(error), Self::Io(other_error)) => error.kind() == other_error.kind(),
66 (Self::HttpError(code), Self::HttpError(other_code)) => code == other_code,
67 _ => false,
68 }
69 }
70}
71
72impl Eq for AssetReaderError {}
73
74impl From<std::io::Error> for AssetReaderError {
75 fn from(value: std::io::Error) -> Self {
76 Self::Io(Arc::new(value))
77 }
78}
79
80pub const STACK_FUTURE_SIZE: usize = 10 * size_of::<&()>();
86
87pub use stackfuture::StackFuture;
88
89pub trait AsyncSeekForward {
95 fn poll_seek_forward(
111 self: Pin<&mut Self>,
112 cx: &mut Context<'_>,
113 offset: u64,
114 ) -> Poll<futures_io::Result<u64>>;
115}
116
117impl<T: ?Sized + AsyncSeekForward + Unpin> AsyncSeekForward for Box<T> {
118 fn poll_seek_forward(
119 mut self: Pin<&mut Self>,
120 cx: &mut Context<'_>,
121 offset: u64,
122 ) -> Poll<futures_io::Result<u64>> {
123 Pin::new(&mut **self).poll_seek_forward(cx, offset)
124 }
125}
126
127pub trait AsyncSeekForwardExt: AsyncSeekForward {
129 fn seek_forward(&mut self, offset: u64) -> SeekForwardFuture<'_, Self>
131 where
132 Self: Unpin,
133 {
134 SeekForwardFuture {
135 seeker: self,
136 offset,
137 }
138 }
139}
140
141impl<R: AsyncSeekForward + ?Sized> AsyncSeekForwardExt for R {}
142
143#[derive(Debug)]
144#[must_use = "futures do nothing unless you `.await` or poll them"]
145pub struct SeekForwardFuture<'a, S: Unpin + ?Sized> {
146 seeker: &'a mut S,
147 offset: u64,
148}
149
150impl<S: Unpin + ?Sized> Unpin for SeekForwardFuture<'_, S> {}
151
152impl<S: AsyncSeekForward + Unpin + ?Sized> Future for SeekForwardFuture<'_, S> {
153 type Output = futures_lite::io::Result<u64>;
154
155 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
156 let offset = self.offset;
157 Pin::new(&mut *self.seeker).poll_seek_forward(cx, offset)
158 }
159}
160
161pub trait Reader: AsyncRead + AsyncSeekForward + Unpin + Send + Sync {
168 fn read_to_end<'a>(
175 &'a mut self,
176 buf: &'a mut Vec<u8>,
177 ) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
178 let future = futures_lite::AsyncReadExt::read_to_end(self, buf);
179 StackFuture::from(future)
180 }
181}
182
183impl Reader for Box<dyn Reader + '_> {
184 fn read_to_end<'a>(
185 &'a mut self,
186 buf: &'a mut Vec<u8>,
187 ) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
188 (**self).read_to_end(buf)
189 }
190}
191
192pub trait AssetReaderFuture:
194 ConditionalSendFuture<Output = Result<Self::Value, AssetReaderError>>
195{
196 type Value;
197}
198
199impl<F, T> AssetReaderFuture for F
200where
201 F: ConditionalSendFuture<Output = Result<T, AssetReaderError>>,
202{
203 type Value = T;
204}
205
206pub trait AssetReader: Send + Sync + 'static {
215 fn read<'a>(&'a self, path: &'a Path) -> impl AssetReaderFuture<Value: Reader + 'a>;
237 fn read_meta<'a>(&'a self, path: &'a Path) -> impl AssetReaderFuture<Value: Reader + 'a>;
239 fn read_directory<'a>(
241 &'a self,
242 path: &'a Path,
243 ) -> impl ConditionalSendFuture<Output = Result<Box<PathStream>, AssetReaderError>>;
244 fn is_directory<'a>(
246 &'a self,
247 path: &'a Path,
248 ) -> impl ConditionalSendFuture<Output = Result<bool, AssetReaderError>>;
249 fn read_meta_bytes<'a>(
252 &'a self,
253 path: &'a Path,
254 ) -> impl ConditionalSendFuture<Output = Result<Vec<u8>, AssetReaderError>> {
255 async {
256 let mut meta_reader = self.read_meta(path).await?;
257 let mut meta_bytes = Vec::new();
258 meta_reader.read_to_end(&mut meta_bytes).await?;
259 Ok(meta_bytes)
260 }
261 }
262}
263
264pub trait ErasedAssetReader: Send + Sync + 'static {
267 fn read<'a>(
269 &'a self,
270 path: &'a Path,
271 ) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>>;
272 fn read_meta<'a>(
274 &'a self,
275 path: &'a Path,
276 ) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>>;
277 fn read_directory<'a>(
279 &'a self,
280 path: &'a Path,
281 ) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>>;
282 fn is_directory<'a>(
284 &'a self,
285 path: &'a Path,
286 ) -> BoxedFuture<'a, Result<bool, AssetReaderError>>;
287 fn read_meta_bytes<'a>(
290 &'a self,
291 path: &'a Path,
292 ) -> BoxedFuture<'a, Result<Vec<u8>, AssetReaderError>>;
293}
294
295impl<T: AssetReader> ErasedAssetReader for T {
296 fn read<'a>(
297 &'a self,
298 path: &'a Path,
299 ) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>> {
300 Box::pin(async {
301 let reader = Self::read(self, path).await?;
302 Ok(Box::new(reader) as Box<dyn Reader>)
303 })
304 }
305 fn read_meta<'a>(
306 &'a self,
307 path: &'a Path,
308 ) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>> {
309 Box::pin(async {
310 let reader = Self::read_meta(self, path).await?;
311 Ok(Box::new(reader) as Box<dyn Reader>)
312 })
313 }
314 fn read_directory<'a>(
315 &'a self,
316 path: &'a Path,
317 ) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>> {
318 Box::pin(Self::read_directory(self, path))
319 }
320 fn is_directory<'a>(
321 &'a self,
322 path: &'a Path,
323 ) -> BoxedFuture<'a, Result<bool, AssetReaderError>> {
324 Box::pin(Self::is_directory(self, path))
325 }
326 fn read_meta_bytes<'a>(
327 &'a self,
328 path: &'a Path,
329 ) -> BoxedFuture<'a, Result<Vec<u8>, AssetReaderError>> {
330 Box::pin(Self::read_meta_bytes(self, path))
331 }
332}
333
334pub type Writer = dyn AsyncWrite + Unpin + Send + Sync;
335
336pub type PathStream = dyn Stream<Item = PathBuf> + Unpin + Send;
337
338#[derive(Error, Debug)]
340pub enum AssetWriterError {
341 #[error("encountered an io error while loading asset: {0}")]
343 Io(#[from] std::io::Error),
344}
345
346pub trait AssetWriter: Send + Sync + 'static {
355 fn write<'a>(
357 &'a self,
358 path: &'a Path,
359 ) -> impl ConditionalSendFuture<Output = Result<Box<Writer>, AssetWriterError>>;
360 fn write_meta<'a>(
363 &'a self,
364 path: &'a Path,
365 ) -> impl ConditionalSendFuture<Output = Result<Box<Writer>, AssetWriterError>>;
366 fn remove<'a>(
368 &'a self,
369 path: &'a Path,
370 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
371 fn remove_meta<'a>(
374 &'a self,
375 path: &'a Path,
376 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
377 fn rename<'a>(
379 &'a self,
380 old_path: &'a Path,
381 new_path: &'a Path,
382 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
383 fn rename_meta<'a>(
386 &'a self,
387 old_path: &'a Path,
388 new_path: &'a Path,
389 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
390 fn create_directory<'a>(
393 &'a self,
394 path: &'a Path,
395 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
396 fn remove_directory<'a>(
398 &'a self,
399 path: &'a Path,
400 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
401 fn remove_empty_directory<'a>(
404 &'a self,
405 path: &'a Path,
406 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
407 fn remove_assets_in_directory<'a>(
409 &'a self,
410 path: &'a Path,
411 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
412 fn write_bytes<'a>(
414 &'a self,
415 path: &'a Path,
416 bytes: &'a [u8],
417 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>> {
418 async {
419 let mut writer = self.write(path).await?;
420 writer.write_all(bytes).await?;
421 writer.flush().await?;
422 Ok(())
423 }
424 }
425 fn write_meta_bytes<'a>(
427 &'a self,
428 path: &'a Path,
429 bytes: &'a [u8],
430 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>> {
431 async {
432 let mut meta_writer = self.write_meta(path).await?;
433 meta_writer.write_all(bytes).await?;
434 meta_writer.flush().await?;
435 Ok(())
436 }
437 }
438}
439
440pub trait ErasedAssetWriter: Send + Sync + 'static {
443 fn write<'a>(
445 &'a self,
446 path: &'a Path,
447 ) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>>;
448 fn write_meta<'a>(
451 &'a self,
452 path: &'a Path,
453 ) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>>;
454 fn remove<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
456 fn remove_meta<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
459 fn rename<'a>(
461 &'a self,
462 old_path: &'a Path,
463 new_path: &'a Path,
464 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
465 fn rename_meta<'a>(
468 &'a self,
469 old_path: &'a Path,
470 new_path: &'a Path,
471 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
472 fn create_directory<'a>(
475 &'a self,
476 path: &'a Path,
477 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
478 fn remove_directory<'a>(
480 &'a self,
481 path: &'a Path,
482 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
483 fn remove_empty_directory<'a>(
486 &'a self,
487 path: &'a Path,
488 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
489 fn remove_assets_in_directory<'a>(
491 &'a self,
492 path: &'a Path,
493 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
494 fn write_bytes<'a>(
496 &'a self,
497 path: &'a Path,
498 bytes: &'a [u8],
499 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
500 fn write_meta_bytes<'a>(
502 &'a self,
503 path: &'a Path,
504 bytes: &'a [u8],
505 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
506}
507
508impl<T: AssetWriter> ErasedAssetWriter for T {
509 fn write<'a>(
510 &'a self,
511 path: &'a Path,
512 ) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>> {
513 Box::pin(Self::write(self, path))
514 }
515 fn write_meta<'a>(
516 &'a self,
517 path: &'a Path,
518 ) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>> {
519 Box::pin(Self::write_meta(self, path))
520 }
521 fn remove<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
522 Box::pin(Self::remove(self, path))
523 }
524 fn remove_meta<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
525 Box::pin(Self::remove_meta(self, path))
526 }
527 fn rename<'a>(
528 &'a self,
529 old_path: &'a Path,
530 new_path: &'a Path,
531 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
532 Box::pin(Self::rename(self, old_path, new_path))
533 }
534 fn rename_meta<'a>(
535 &'a self,
536 old_path: &'a Path,
537 new_path: &'a Path,
538 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
539 Box::pin(Self::rename_meta(self, old_path, new_path))
540 }
541 fn create_directory<'a>(
542 &'a self,
543 path: &'a Path,
544 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
545 Box::pin(Self::create_directory(self, path))
546 }
547 fn remove_directory<'a>(
548 &'a self,
549 path: &'a Path,
550 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
551 Box::pin(Self::remove_directory(self, path))
552 }
553 fn remove_empty_directory<'a>(
554 &'a self,
555 path: &'a Path,
556 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
557 Box::pin(Self::remove_empty_directory(self, path))
558 }
559 fn remove_assets_in_directory<'a>(
560 &'a self,
561 path: &'a Path,
562 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
563 Box::pin(Self::remove_assets_in_directory(self, path))
564 }
565 fn write_bytes<'a>(
566 &'a self,
567 path: &'a Path,
568 bytes: &'a [u8],
569 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
570 Box::pin(Self::write_bytes(self, path, bytes))
571 }
572 fn write_meta_bytes<'a>(
573 &'a self,
574 path: &'a Path,
575 bytes: &'a [u8],
576 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
577 Box::pin(Self::write_meta_bytes(self, path, bytes))
578 }
579}
580
581#[derive(Clone, Debug, PartialEq, Eq)]
583pub enum AssetSourceEvent {
584 AddedAsset(PathBuf),
586 ModifiedAsset(PathBuf),
588 RemovedAsset(PathBuf),
590 RenamedAsset { old: PathBuf, new: PathBuf },
592 AddedMeta(PathBuf),
594 ModifiedMeta(PathBuf),
596 RemovedMeta(PathBuf),
598 RenamedMeta { old: PathBuf, new: PathBuf },
600 AddedFolder(PathBuf),
602 RemovedFolder(PathBuf),
604 RenamedFolder { old: PathBuf, new: PathBuf },
606 RemovedUnknown {
610 path: PathBuf,
612 is_meta: bool,
616 },
617}
618
619pub trait AssetWatcher: Send + Sync + 'static {}
622
623pub struct VecReader {
625 bytes: Vec<u8>,
626 bytes_read: usize,
627}
628
629impl VecReader {
630 pub fn new(bytes: Vec<u8>) -> Self {
632 Self {
633 bytes_read: 0,
634 bytes,
635 }
636 }
637}
638
639impl AsyncRead for VecReader {
640 fn poll_read(
641 mut self: Pin<&mut Self>,
642 cx: &mut Context<'_>,
643 buf: &mut [u8],
644 ) -> Poll<futures_io::Result<usize>> {
645 if self.bytes_read >= self.bytes.len() {
646 Poll::Ready(Ok(0))
647 } else {
648 let n = ready!(Pin::new(&mut &self.bytes[self.bytes_read..]).poll_read(cx, buf))?;
649 self.bytes_read += n;
650 Poll::Ready(Ok(n))
651 }
652 }
653}
654
655impl AsyncSeekForward for VecReader {
656 fn poll_seek_forward(
657 mut self: Pin<&mut Self>,
658 _cx: &mut Context<'_>,
659 offset: u64,
660 ) -> Poll<std::io::Result<u64>> {
661 let result = self
662 .bytes_read
663 .try_into()
664 .map(|bytes_read: u64| bytes_read + offset);
665
666 if let Ok(new_pos) = result {
667 self.bytes_read = new_pos as _;
668 Poll::Ready(Ok(new_pos as _))
669 } else {
670 Poll::Ready(Err(std::io::Error::new(
671 std::io::ErrorKind::InvalidInput,
672 "seek position is out of range",
673 )))
674 }
675 }
676}
677
678impl Reader for VecReader {
679 fn read_to_end<'a>(
680 &'a mut self,
681 buf: &'a mut Vec<u8>,
682 ) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
683 StackFuture::from(async {
684 if self.bytes_read >= self.bytes.len() {
685 Ok(0)
686 } else {
687 buf.extend_from_slice(&self.bytes[self.bytes_read..]);
688 let n = self.bytes.len() - self.bytes_read;
689 self.bytes_read = self.bytes.len();
690 Ok(n)
691 }
692 })
693 }
694}
695
696pub struct SliceReader<'a> {
698 bytes: &'a [u8],
699 bytes_read: usize,
700}
701
702impl<'a> SliceReader<'a> {
703 pub fn new(bytes: &'a [u8]) -> Self {
705 Self {
706 bytes,
707 bytes_read: 0,
708 }
709 }
710}
711
712impl<'a> AsyncRead for SliceReader<'a> {
713 fn poll_read(
714 mut self: Pin<&mut Self>,
715 cx: &mut Context<'_>,
716 buf: &mut [u8],
717 ) -> Poll<std::io::Result<usize>> {
718 if self.bytes_read >= self.bytes.len() {
719 Poll::Ready(Ok(0))
720 } else {
721 let n = ready!(Pin::new(&mut &self.bytes[self.bytes_read..]).poll_read(cx, buf))?;
722 self.bytes_read += n;
723 Poll::Ready(Ok(n))
724 }
725 }
726}
727
728impl<'a> AsyncSeekForward for SliceReader<'a> {
729 fn poll_seek_forward(
730 mut self: Pin<&mut Self>,
731 _cx: &mut Context<'_>,
732 offset: u64,
733 ) -> Poll<std::io::Result<u64>> {
734 let result = self
735 .bytes_read
736 .try_into()
737 .map(|bytes_read: u64| bytes_read + offset);
738
739 if let Ok(new_pos) = result {
740 self.bytes_read = new_pos as _;
741
742 Poll::Ready(Ok(new_pos as _))
743 } else {
744 Poll::Ready(Err(std::io::Error::new(
745 std::io::ErrorKind::InvalidInput,
746 "seek position is out of range",
747 )))
748 }
749 }
750}
751
752impl Reader for SliceReader<'_> {
753 fn read_to_end<'a>(
754 &'a mut self,
755 buf: &'a mut Vec<u8>,
756 ) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
757 StackFuture::from(async {
758 if self.bytes_read >= self.bytes.len() {
759 Ok(0)
760 } else {
761 buf.extend_from_slice(&self.bytes[self.bytes_read..]);
762 let n = self.bytes.len() - self.bytes_read;
763 self.bytes_read = self.bytes.len();
764 Ok(n)
765 }
766 })
767 }
768}
769
770pub(crate) fn get_meta_path(path: &Path) -> PathBuf {
774 let mut meta_path = path.to_path_buf();
775 let mut extension = path.extension().unwrap_or_default().to_os_string();
776 if !extension.is_empty() {
777 extension.push(".");
778 }
779 extension.push("meta");
780 meta_path.set_extension(extension);
781 meta_path
782}
783
784#[cfg(any(target_arch = "wasm32", target_os = "android"))]
785struct EmptyPathStream;
787
788#[cfg(any(target_arch = "wasm32", target_os = "android"))]
789impl Stream for EmptyPathStream {
790 type Item = PathBuf;
791
792 fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
793 Poll::Ready(None)
794 }
795}
796
797#[cfg(test)]
798mod tests {
799 use super::*;
800
801 #[test]
802 fn get_meta_path_no_extension() {
803 assert_eq!(
804 get_meta_path(Path::new("foo")).to_str().unwrap(),
805 "foo.meta"
806 );
807 }
808
809 #[test]
810 fn get_meta_path_with_extension() {
811 assert_eq!(
812 get_meta_path(Path::new("foo.bar")).to_str().unwrap(),
813 "foo.bar.meta"
814 );
815 }
816}