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::{
31 mem::size_of,
32 pin::Pin,
33 task::{Context, Poll},
34};
35use futures_io::{AsyncRead, AsyncSeek, AsyncWrite};
36use futures_lite::Stream;
37use std::{
38 io::SeekFrom,
39 path::{Path, PathBuf},
40};
41use thiserror::Error;
42
43#[derive(Error, Debug, Clone)]
45pub enum AssetReaderError {
46 #[error("Path not found: {}", _0.display())]
48 NotFound(PathBuf),
49
50 #[error("Encountered an I/O error while loading asset: {0}")]
52 Io(Arc<std::io::Error>),
53
54 #[error("Encountered HTTP status {0:?} when loading asset")]
58 HttpError(u16),
59}
60
61impl PartialEq for AssetReaderError {
62 #[inline]
64 fn eq(&self, other: &Self) -> bool {
65 match (self, other) {
66 (Self::NotFound(path), Self::NotFound(other_path)) => path == other_path,
67 (Self::Io(error), Self::Io(other_error)) => error.kind() == other_error.kind(),
68 (Self::HttpError(code), Self::HttpError(other_code)) => code == other_code,
69 _ => false,
70 }
71 }
72}
73
74impl Eq for AssetReaderError {}
75
76impl From<std::io::Error> for AssetReaderError {
77 fn from(value: std::io::Error) -> Self {
78 Self::Io(Arc::new(value))
79 }
80}
81
82pub const STACK_FUTURE_SIZE: usize = 10 * size_of::<&()>();
88
89pub use stackfuture::StackFuture;
90
91pub trait Reader: AsyncRead + Unpin + Send + Sync {
98 fn read_to_end<'a>(
105 &'a mut self,
106 buf: &'a mut Vec<u8>,
107 ) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
108 let future = futures_lite::AsyncReadExt::read_to_end(self, buf);
109 StackFuture::from(future)
110 }
111
112 fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError>;
140}
141
142pub trait SeekableReader: Reader + AsyncSeek {}
145
146impl<T: Reader + AsyncSeek> SeekableReader for T {}
147
148#[derive(Error, Debug, Copy, Clone)]
150#[error(
151 "The `Reader` returned by the current `AssetReader` does not support `AsyncSeek` behavior."
152)]
153pub struct ReaderNotSeekableError;
154
155impl Reader for Box<dyn Reader + '_> {
156 fn read_to_end<'a>(
157 &'a mut self,
158 buf: &'a mut Vec<u8>,
159 ) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
160 (**self).read_to_end(buf)
161 }
162
163 fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError> {
164 (**self).seekable()
165 }
166}
167
168pub trait AssetReaderFuture:
170 ConditionalSendFuture<Output = Result<Self::Value, AssetReaderError>>
171{
172 type Value;
173}
174
175impl<F, T> AssetReaderFuture for F
176where
177 F: ConditionalSendFuture<Output = Result<T, AssetReaderError>>,
178{
179 type Value = T;
180}
181
182pub trait AssetReader: Send + Sync + 'static {
191 fn read<'a>(&'a self, path: &'a Path) -> impl AssetReaderFuture<Value: Reader + 'a>;
213 fn read_meta<'a>(&'a self, path: &'a Path) -> impl AssetReaderFuture<Value: Reader + 'a>;
215 fn read_directory<'a>(
217 &'a self,
218 path: &'a Path,
219 ) -> impl ConditionalSendFuture<Output = Result<Box<PathStream>, AssetReaderError>>;
220 fn is_directory<'a>(
222 &'a self,
223 path: &'a Path,
224 ) -> impl ConditionalSendFuture<Output = Result<bool, AssetReaderError>>;
225 fn read_meta_bytes<'a>(
228 &'a self,
229 path: &'a Path,
230 ) -> impl ConditionalSendFuture<Output = Result<Vec<u8>, AssetReaderError>> {
231 async {
232 let mut meta_reader = self.read_meta(path).await?;
233 let mut meta_bytes = Vec::new();
234 meta_reader.read_to_end(&mut meta_bytes).await?;
235 Ok(meta_bytes)
236 }
237 }
238}
239
240pub trait ErasedAssetReader: Send + Sync + 'static {
243 fn read<'a>(
245 &'a self,
246 path: &'a Path,
247 ) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>>;
248 fn read_meta<'a>(
250 &'a self,
251 path: &'a Path,
252 ) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>>;
253 fn read_directory<'a>(
255 &'a self,
256 path: &'a Path,
257 ) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>>;
258 fn is_directory<'a>(
260 &'a self,
261 path: &'a Path,
262 ) -> BoxedFuture<'a, Result<bool, AssetReaderError>>;
263 fn read_meta_bytes<'a>(
266 &'a self,
267 path: &'a Path,
268 ) -> BoxedFuture<'a, Result<Vec<u8>, AssetReaderError>>;
269}
270
271impl<T: AssetReader> ErasedAssetReader for T {
272 fn read<'a>(
273 &'a self,
274 path: &'a Path,
275 ) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>> {
276 Box::pin(async move {
277 let reader = Self::read(self, path).await?;
278 Ok(Box::new(reader) as Box<dyn Reader>)
279 })
280 }
281 fn read_meta<'a>(
282 &'a self,
283 path: &'a Path,
284 ) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>> {
285 Box::pin(async {
286 let reader = Self::read_meta(self, path).await?;
287 Ok(Box::new(reader) as Box<dyn Reader>)
288 })
289 }
290 fn read_directory<'a>(
291 &'a self,
292 path: &'a Path,
293 ) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>> {
294 Box::pin(Self::read_directory(self, path))
295 }
296 fn is_directory<'a>(
297 &'a self,
298 path: &'a Path,
299 ) -> BoxedFuture<'a, Result<bool, AssetReaderError>> {
300 Box::pin(Self::is_directory(self, path))
301 }
302 fn read_meta_bytes<'a>(
303 &'a self,
304 path: &'a Path,
305 ) -> BoxedFuture<'a, Result<Vec<u8>, AssetReaderError>> {
306 Box::pin(Self::read_meta_bytes(self, path))
307 }
308}
309
310pub type Writer = dyn AsyncWrite + Unpin + Send + Sync;
311
312pub type PathStream = dyn Stream<Item = PathBuf> + Unpin + Send;
313
314#[derive(Error, Debug)]
316pub enum AssetWriterError {
317 #[error("encountered an io error while loading asset: {0}")]
319 Io(#[from] std::io::Error),
320}
321
322pub trait AssetWriter: Send + Sync + 'static {
331 fn write<'a>(
333 &'a self,
334 path: &'a Path,
335 ) -> impl ConditionalSendFuture<Output = Result<Box<Writer>, AssetWriterError>>;
336 fn write_meta<'a>(
339 &'a self,
340 path: &'a Path,
341 ) -> impl ConditionalSendFuture<Output = Result<Box<Writer>, AssetWriterError>>;
342 fn remove<'a>(
344 &'a self,
345 path: &'a Path,
346 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
347 fn remove_meta<'a>(
350 &'a self,
351 path: &'a Path,
352 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
353 fn rename<'a>(
355 &'a self,
356 old_path: &'a Path,
357 new_path: &'a Path,
358 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
359 fn rename_meta<'a>(
362 &'a self,
363 old_path: &'a Path,
364 new_path: &'a Path,
365 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
366 fn create_directory<'a>(
369 &'a self,
370 path: &'a Path,
371 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
372 fn remove_directory<'a>(
374 &'a self,
375 path: &'a Path,
376 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
377 fn remove_empty_directory<'a>(
380 &'a self,
381 path: &'a Path,
382 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
383 fn remove_assets_in_directory<'a>(
385 &'a self,
386 path: &'a Path,
387 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
388 fn write_bytes<'a>(
390 &'a self,
391 path: &'a Path,
392 bytes: &'a [u8],
393 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>> {
394 async {
395 let mut writer = self.write(path).await?;
396 writer.write_all(bytes).await?;
397 writer.flush().await?;
398 Ok(())
399 }
400 }
401 fn write_meta_bytes<'a>(
403 &'a self,
404 path: &'a Path,
405 bytes: &'a [u8],
406 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>> {
407 async {
408 let mut meta_writer = self.write_meta(path).await?;
409 meta_writer.write_all(bytes).await?;
410 meta_writer.flush().await?;
411 Ok(())
412 }
413 }
414}
415
416pub trait ErasedAssetWriter: Send + Sync + 'static {
419 fn write<'a>(
421 &'a self,
422 path: &'a Path,
423 ) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>>;
424 fn write_meta<'a>(
427 &'a self,
428 path: &'a Path,
429 ) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>>;
430 fn remove<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
432 fn remove_meta<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
435 fn rename<'a>(
437 &'a self,
438 old_path: &'a Path,
439 new_path: &'a Path,
440 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
441 fn rename_meta<'a>(
444 &'a self,
445 old_path: &'a Path,
446 new_path: &'a Path,
447 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
448 fn create_directory<'a>(
451 &'a self,
452 path: &'a Path,
453 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
454 fn remove_directory<'a>(
456 &'a self,
457 path: &'a Path,
458 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
459 fn remove_empty_directory<'a>(
462 &'a self,
463 path: &'a Path,
464 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
465 fn remove_assets_in_directory<'a>(
467 &'a self,
468 path: &'a Path,
469 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
470 fn write_bytes<'a>(
472 &'a self,
473 path: &'a Path,
474 bytes: &'a [u8],
475 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
476 fn write_meta_bytes<'a>(
478 &'a self,
479 path: &'a Path,
480 bytes: &'a [u8],
481 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
482}
483
484impl<T: AssetWriter> ErasedAssetWriter for T {
485 fn write<'a>(
486 &'a self,
487 path: &'a Path,
488 ) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>> {
489 Box::pin(Self::write(self, path))
490 }
491 fn write_meta<'a>(
492 &'a self,
493 path: &'a Path,
494 ) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>> {
495 Box::pin(Self::write_meta(self, path))
496 }
497 fn remove<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
498 Box::pin(Self::remove(self, path))
499 }
500 fn remove_meta<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
501 Box::pin(Self::remove_meta(self, path))
502 }
503 fn rename<'a>(
504 &'a self,
505 old_path: &'a Path,
506 new_path: &'a Path,
507 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
508 Box::pin(Self::rename(self, old_path, new_path))
509 }
510 fn rename_meta<'a>(
511 &'a self,
512 old_path: &'a Path,
513 new_path: &'a Path,
514 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
515 Box::pin(Self::rename_meta(self, old_path, new_path))
516 }
517 fn create_directory<'a>(
518 &'a self,
519 path: &'a Path,
520 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
521 Box::pin(Self::create_directory(self, path))
522 }
523 fn remove_directory<'a>(
524 &'a self,
525 path: &'a Path,
526 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
527 Box::pin(Self::remove_directory(self, path))
528 }
529 fn remove_empty_directory<'a>(
530 &'a self,
531 path: &'a Path,
532 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
533 Box::pin(Self::remove_empty_directory(self, path))
534 }
535 fn remove_assets_in_directory<'a>(
536 &'a self,
537 path: &'a Path,
538 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
539 Box::pin(Self::remove_assets_in_directory(self, path))
540 }
541 fn write_bytes<'a>(
542 &'a self,
543 path: &'a Path,
544 bytes: &'a [u8],
545 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
546 Box::pin(Self::write_bytes(self, path, bytes))
547 }
548 fn write_meta_bytes<'a>(
549 &'a self,
550 path: &'a Path,
551 bytes: &'a [u8],
552 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
553 Box::pin(Self::write_meta_bytes(self, path, bytes))
554 }
555}
556
557#[derive(Clone, Debug, PartialEq, Eq)]
559pub enum AssetSourceEvent {
560 AddedAsset(PathBuf),
562 ModifiedAsset(PathBuf),
564 RemovedAsset(PathBuf),
566 RenamedAsset { old: PathBuf, new: PathBuf },
568 AddedMeta(PathBuf),
570 ModifiedMeta(PathBuf),
572 RemovedMeta(PathBuf),
574 RenamedMeta { old: PathBuf, new: PathBuf },
576 AddedFolder(PathBuf),
578 RemovedFolder(PathBuf),
580 RenamedFolder { old: PathBuf, new: PathBuf },
582 RemovedUnknown {
586 path: PathBuf,
588 is_meta: bool,
592 },
593}
594
595pub trait AssetWatcher: Send + Sync + 'static {}
598
599pub struct VecReader {
601 pub bytes: Vec<u8>,
603 bytes_read: usize,
604}
605
606impl VecReader {
607 pub fn new(bytes: Vec<u8>) -> Self {
609 Self {
610 bytes_read: 0,
611 bytes,
612 }
613 }
614}
615
616impl AsyncRead for VecReader {
617 fn poll_read(
618 self: Pin<&mut Self>,
619 _cx: &mut Context<'_>,
620 buf: &mut [u8],
621 ) -> Poll<futures_io::Result<usize>> {
622 let this = self.get_mut();
624 Poll::Ready(Ok(slice_read(&this.bytes, &mut this.bytes_read, buf)))
625 }
626}
627
628impl AsyncSeek for VecReader {
629 fn poll_seek(
630 self: Pin<&mut Self>,
631 _cx: &mut Context<'_>,
632 pos: SeekFrom,
633 ) -> Poll<std::io::Result<u64>> {
634 let this = self.get_mut();
636 Poll::Ready(slice_seek(&this.bytes, &mut this.bytes_read, pos))
637 }
638}
639
640impl Reader for VecReader {
641 fn read_to_end<'a>(
642 &'a mut self,
643 buf: &'a mut Vec<u8>,
644 ) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
645 read_to_end(&self.bytes, &mut self.bytes_read, buf)
646 }
647
648 fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError> {
649 Ok(self)
650 }
651}
652
653pub struct SliceReader<'a> {
655 bytes: &'a [u8],
656 bytes_read: usize,
657}
658
659impl<'a> SliceReader<'a> {
660 pub fn new(bytes: &'a [u8]) -> Self {
662 Self {
663 bytes,
664 bytes_read: 0,
665 }
666 }
667}
668
669impl<'a> AsyncRead for SliceReader<'a> {
670 fn poll_read(
671 mut self: Pin<&mut Self>,
672 _cx: &mut Context<'_>,
673 buf: &mut [u8],
674 ) -> Poll<std::io::Result<usize>> {
675 Poll::Ready(Ok(slice_read(self.bytes, &mut self.bytes_read, buf)))
676 }
677}
678
679impl<'a> AsyncSeek for SliceReader<'a> {
680 fn poll_seek(
681 mut self: Pin<&mut Self>,
682 _cx: &mut Context<'_>,
683 pos: SeekFrom,
684 ) -> Poll<std::io::Result<u64>> {
685 Poll::Ready(slice_seek(self.bytes, &mut self.bytes_read, pos))
686 }
687}
688
689impl Reader for SliceReader<'_> {
690 fn read_to_end<'a>(
691 &'a mut self,
692 buf: &'a mut Vec<u8>,
693 ) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
694 read_to_end(self.bytes, &mut self.bytes_read, buf)
695 }
696
697 fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError> {
698 Ok(self)
699 }
700}
701
702pub(crate) fn slice_read(slice: &[u8], bytes_read: &mut usize, buf: &mut [u8]) -> usize {
704 if *bytes_read >= slice.len() {
705 0
706 } else {
707 let n = std::io::Read::read(&mut &slice[(*bytes_read)..], buf).unwrap();
708 *bytes_read += n;
709 n
710 }
711}
712
713pub(crate) fn slice_seek(
715 slice: &[u8],
716 bytes_read: &mut usize,
717 pos: SeekFrom,
718) -> std::io::Result<u64> {
719 let make_error = || {
720 Err(std::io::Error::new(
721 std::io::ErrorKind::InvalidInput,
722 "seek position is out of range",
723 ))
724 };
725 let (origin, offset) = match pos {
726 SeekFrom::Current(offset) => (*bytes_read, Ok(offset)),
727 SeekFrom::Start(offset) => (0, offset.try_into()),
728 SeekFrom::End(offset) => (slice.len(), Ok(offset)),
729 };
730 let Ok(offset) = offset else {
731 return make_error();
732 };
733 let Ok(origin): Result<i64, _> = origin.try_into() else {
734 return make_error();
735 };
736 let Ok(new_pos) = (origin + offset).try_into() else {
737 return make_error();
738 };
739 *bytes_read = new_pos;
740 Ok(new_pos as _)
741}
742
743pub(crate) fn read_to_end<'a>(
748 source: &'a [u8],
749 bytes_read: &'a mut usize,
750 dest: &'a mut Vec<u8>,
751) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
752 StackFuture::from(async {
753 if *bytes_read >= source.len() {
754 Ok(0)
755 } else {
756 dest.extend_from_slice(&source[*bytes_read..]);
757 let n = source.len() - *bytes_read;
758 *bytes_read = source.len();
759 Ok(n)
760 }
761 })
762}
763
764pub(crate) fn get_meta_path(path: &Path) -> PathBuf {
768 let mut meta_path = path.to_path_buf();
769 let mut extension = path.extension().unwrap_or_default().to_os_string();
770 if !extension.is_empty() {
771 extension.push(".");
772 }
773 extension.push("meta");
774 meta_path.set_extension(extension);
775 meta_path
776}
777
778#[cfg(any(target_arch = "wasm32", target_os = "android"))]
779struct EmptyPathStream;
781
782#[cfg(any(target_arch = "wasm32", target_os = "android"))]
783impl Stream for EmptyPathStream {
784 type Item = PathBuf;
785
786 fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
787 Poll::Ready(None)
788 }
789}
790
791#[cfg(test)]
792mod tests {
793 use super::*;
794
795 #[test]
796 fn get_meta_path_no_extension() {
797 assert_eq!(
798 get_meta_path(Path::new("foo")).to_str().unwrap(),
799 "foo.meta"
800 );
801 }
802
803 #[test]
804 fn get_meta_path_with_extension() {
805 assert_eq!(
806 get_meta_path(Path::new("foo.bar")).to_str().unwrap(),
807 "foo.bar.meta"
808 );
809 }
810}