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::sync::Arc;
25use bevy_utils::{BoxedFuture, ConditionalSendFuture};
26use core::future::Future;
27use core::{
28 mem::size_of,
29 pin::Pin,
30 task::{Context, Poll},
31};
32use derive_more::derive::{Display, Error, From};
33use futures_io::{AsyncRead, AsyncWrite};
34use futures_lite::{ready, Stream};
35use std::path::{Path, PathBuf};
36
37#[derive(Error, Display, Debug, Clone)]
39pub enum AssetReaderError {
40 #[display("Path not found: {}", _0.display())]
42 #[error(ignore)]
43 NotFound(PathBuf),
44
45 #[display("Encountered an I/O error while loading asset: {_0}")]
47 Io(Arc<std::io::Error>),
48
49 #[display("Encountered HTTP status {_0:?} when loading asset")]
52 #[error(ignore)]
53 HttpError(u16),
54}
55
56impl PartialEq for AssetReaderError {
57 #[inline]
59 fn eq(&self, other: &Self) -> bool {
60 match (self, other) {
61 (Self::NotFound(path), Self::NotFound(other_path)) => path == other_path,
62 (Self::Io(error), Self::Io(other_error)) => error.kind() == other_error.kind(),
63 (Self::HttpError(code), Self::HttpError(other_code)) => code == other_code,
64 _ => false,
65 }
66 }
67}
68
69impl Eq for AssetReaderError {}
70
71impl From<std::io::Error> for AssetReaderError {
72 fn from(value: std::io::Error) -> Self {
73 Self::Io(Arc::new(value))
74 }
75}
76
77pub const STACK_FUTURE_SIZE: usize = 10 * size_of::<&()>();
83
84pub use stackfuture::StackFuture;
85
86pub trait AsyncSeekForward {
92 fn poll_seek_forward(
108 self: Pin<&mut Self>,
109 cx: &mut Context<'_>,
110 offset: u64,
111 ) -> Poll<futures_io::Result<u64>>;
112}
113
114impl<T: ?Sized + AsyncSeekForward + Unpin> AsyncSeekForward for Box<T> {
115 fn poll_seek_forward(
116 mut self: Pin<&mut Self>,
117 cx: &mut Context<'_>,
118 offset: u64,
119 ) -> Poll<futures_io::Result<u64>> {
120 Pin::new(&mut **self).poll_seek_forward(cx, offset)
121 }
122}
123
124pub trait AsyncSeekForwardExt: AsyncSeekForward {
126 fn seek_forward(&mut self, offset: u64) -> SeekForwardFuture<'_, Self>
128 where
129 Self: Unpin,
130 {
131 SeekForwardFuture {
132 seeker: self,
133 offset,
134 }
135 }
136}
137
138impl<R: AsyncSeekForward + ?Sized> AsyncSeekForwardExt for R {}
139
140#[derive(Debug)]
141#[must_use = "futures do nothing unless you `.await` or poll them"]
142pub struct SeekForwardFuture<'a, S: Unpin + ?Sized> {
143 seeker: &'a mut S,
144 offset: u64,
145}
146
147impl<S: Unpin + ?Sized> Unpin for SeekForwardFuture<'_, S> {}
148
149impl<S: AsyncSeekForward + Unpin + ?Sized> Future for SeekForwardFuture<'_, S> {
150 type Output = futures_lite::io::Result<u64>;
151
152 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
153 let offset = self.offset;
154 Pin::new(&mut *self.seeker).poll_seek_forward(cx, offset)
155 }
156}
157
158pub trait Reader: AsyncRead + AsyncSeekForward + Unpin + Send + Sync {
165 fn read_to_end<'a>(
172 &'a mut self,
173 buf: &'a mut Vec<u8>,
174 ) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
175 let future = futures_lite::AsyncReadExt::read_to_end(self, buf);
176 StackFuture::from(future)
177 }
178}
179
180impl Reader for Box<dyn Reader + '_> {
181 fn read_to_end<'a>(
182 &'a mut self,
183 buf: &'a mut Vec<u8>,
184 ) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
185 (**self).read_to_end(buf)
186 }
187}
188
189pub trait AssetReaderFuture:
191 ConditionalSendFuture<Output = Result<Self::Value, AssetReaderError>>
192{
193 type Value;
194}
195
196impl<F, T> AssetReaderFuture for F
197where
198 F: ConditionalSendFuture<Output = Result<T, AssetReaderError>>,
199{
200 type Value = T;
201}
202
203pub trait AssetReader: Send + Sync + 'static {
212 fn read<'a>(&'a self, path: &'a Path) -> impl AssetReaderFuture<Value: Reader + 'a>;
234 fn read_meta<'a>(&'a self, path: &'a Path) -> impl AssetReaderFuture<Value: Reader + 'a>;
236 fn read_directory<'a>(
238 &'a self,
239 path: &'a Path,
240 ) -> impl ConditionalSendFuture<Output = Result<Box<PathStream>, AssetReaderError>>;
241 fn is_directory<'a>(
243 &'a self,
244 path: &'a Path,
245 ) -> impl ConditionalSendFuture<Output = Result<bool, AssetReaderError>>;
246 fn read_meta_bytes<'a>(
249 &'a self,
250 path: &'a Path,
251 ) -> impl ConditionalSendFuture<Output = Result<Vec<u8>, AssetReaderError>> {
252 async {
253 let mut meta_reader = self.read_meta(path).await?;
254 let mut meta_bytes = Vec::new();
255 meta_reader.read_to_end(&mut meta_bytes).await?;
256 Ok(meta_bytes)
257 }
258 }
259}
260
261pub trait ErasedAssetReader: Send + Sync + 'static {
264 fn read<'a>(
266 &'a self,
267 path: &'a Path,
268 ) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>>;
269 fn read_meta<'a>(
271 &'a self,
272 path: &'a Path,
273 ) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>>;
274 fn read_directory<'a>(
276 &'a self,
277 path: &'a Path,
278 ) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>>;
279 fn is_directory<'a>(
281 &'a self,
282 path: &'a Path,
283 ) -> BoxedFuture<'a, Result<bool, AssetReaderError>>;
284 fn read_meta_bytes<'a>(
287 &'a self,
288 path: &'a Path,
289 ) -> BoxedFuture<'a, Result<Vec<u8>, AssetReaderError>>;
290}
291
292impl<T: AssetReader> ErasedAssetReader for T {
293 fn read<'a>(
294 &'a self,
295 path: &'a Path,
296 ) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>> {
297 Box::pin(async {
298 let reader = Self::read(self, path).await?;
299 Ok(Box::new(reader) as Box<dyn Reader>)
300 })
301 }
302 fn read_meta<'a>(
303 &'a self,
304 path: &'a Path,
305 ) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>> {
306 Box::pin(async {
307 let reader = Self::read_meta(self, path).await?;
308 Ok(Box::new(reader) as Box<dyn Reader>)
309 })
310 }
311 fn read_directory<'a>(
312 &'a self,
313 path: &'a Path,
314 ) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>> {
315 Box::pin(Self::read_directory(self, path))
316 }
317 fn is_directory<'a>(
318 &'a self,
319 path: &'a Path,
320 ) -> BoxedFuture<'a, Result<bool, AssetReaderError>> {
321 Box::pin(Self::is_directory(self, path))
322 }
323 fn read_meta_bytes<'a>(
324 &'a self,
325 path: &'a Path,
326 ) -> BoxedFuture<'a, Result<Vec<u8>, AssetReaderError>> {
327 Box::pin(Self::read_meta_bytes(self, path))
328 }
329}
330
331pub type Writer = dyn AsyncWrite + Unpin + Send + Sync;
332
333pub type PathStream = dyn Stream<Item = PathBuf> + Unpin + Send;
334
335#[derive(Error, Display, Debug, From)]
337pub enum AssetWriterError {
338 #[display("encountered an io error while loading asset: {_0}")]
340 Io(std::io::Error),
341}
342
343pub trait AssetWriter: Send + Sync + 'static {
352 fn write<'a>(
354 &'a self,
355 path: &'a Path,
356 ) -> impl ConditionalSendFuture<Output = Result<Box<Writer>, AssetWriterError>>;
357 fn write_meta<'a>(
360 &'a self,
361 path: &'a Path,
362 ) -> impl ConditionalSendFuture<Output = Result<Box<Writer>, AssetWriterError>>;
363 fn remove<'a>(
365 &'a self,
366 path: &'a Path,
367 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
368 fn remove_meta<'a>(
371 &'a self,
372 path: &'a Path,
373 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
374 fn rename<'a>(
376 &'a self,
377 old_path: &'a Path,
378 new_path: &'a Path,
379 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
380 fn rename_meta<'a>(
383 &'a self,
384 old_path: &'a Path,
385 new_path: &'a Path,
386 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
387 fn create_directory<'a>(
390 &'a self,
391 path: &'a Path,
392 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
393 fn remove_directory<'a>(
395 &'a self,
396 path: &'a Path,
397 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
398 fn remove_empty_directory<'a>(
401 &'a self,
402 path: &'a Path,
403 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
404 fn remove_assets_in_directory<'a>(
406 &'a self,
407 path: &'a Path,
408 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
409 fn write_bytes<'a>(
411 &'a self,
412 path: &'a Path,
413 bytes: &'a [u8],
414 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>> {
415 async {
416 let mut writer = self.write(path).await?;
417 writer.write_all(bytes).await?;
418 writer.flush().await?;
419 Ok(())
420 }
421 }
422 fn write_meta_bytes<'a>(
424 &'a self,
425 path: &'a Path,
426 bytes: &'a [u8],
427 ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>> {
428 async {
429 let mut meta_writer = self.write_meta(path).await?;
430 meta_writer.write_all(bytes).await?;
431 meta_writer.flush().await?;
432 Ok(())
433 }
434 }
435}
436
437pub trait ErasedAssetWriter: Send + Sync + 'static {
440 fn write<'a>(
442 &'a self,
443 path: &'a Path,
444 ) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>>;
445 fn write_meta<'a>(
448 &'a self,
449 path: &'a Path,
450 ) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>>;
451 fn remove<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
453 fn remove_meta<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
456 fn rename<'a>(
458 &'a self,
459 old_path: &'a Path,
460 new_path: &'a Path,
461 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
462 fn rename_meta<'a>(
465 &'a self,
466 old_path: &'a Path,
467 new_path: &'a Path,
468 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
469 fn create_directory<'a>(
472 &'a self,
473 path: &'a Path,
474 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
475 fn remove_directory<'a>(
477 &'a self,
478 path: &'a Path,
479 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
480 fn remove_empty_directory<'a>(
483 &'a self,
484 path: &'a Path,
485 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
486 fn remove_assets_in_directory<'a>(
488 &'a self,
489 path: &'a Path,
490 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
491 fn write_bytes<'a>(
493 &'a self,
494 path: &'a Path,
495 bytes: &'a [u8],
496 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
497 fn write_meta_bytes<'a>(
499 &'a self,
500 path: &'a Path,
501 bytes: &'a [u8],
502 ) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
503}
504
505impl<T: AssetWriter> ErasedAssetWriter for T {
506 fn write<'a>(
507 &'a self,
508 path: &'a Path,
509 ) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>> {
510 Box::pin(Self::write(self, path))
511 }
512 fn write_meta<'a>(
513 &'a self,
514 path: &'a Path,
515 ) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>> {
516 Box::pin(Self::write_meta(self, path))
517 }
518 fn remove<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
519 Box::pin(Self::remove(self, path))
520 }
521 fn remove_meta<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
522 Box::pin(Self::remove_meta(self, path))
523 }
524 fn rename<'a>(
525 &'a self,
526 old_path: &'a Path,
527 new_path: &'a Path,
528 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
529 Box::pin(Self::rename(self, old_path, new_path))
530 }
531 fn rename_meta<'a>(
532 &'a self,
533 old_path: &'a Path,
534 new_path: &'a Path,
535 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
536 Box::pin(Self::rename_meta(self, old_path, new_path))
537 }
538 fn create_directory<'a>(
539 &'a self,
540 path: &'a Path,
541 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
542 Box::pin(Self::create_directory(self, path))
543 }
544 fn remove_directory<'a>(
545 &'a self,
546 path: &'a Path,
547 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
548 Box::pin(Self::remove_directory(self, path))
549 }
550 fn remove_empty_directory<'a>(
551 &'a self,
552 path: &'a Path,
553 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
554 Box::pin(Self::remove_empty_directory(self, path))
555 }
556 fn remove_assets_in_directory<'a>(
557 &'a self,
558 path: &'a Path,
559 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
560 Box::pin(Self::remove_assets_in_directory(self, path))
561 }
562 fn write_bytes<'a>(
563 &'a self,
564 path: &'a Path,
565 bytes: &'a [u8],
566 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
567 Box::pin(Self::write_bytes(self, path, bytes))
568 }
569 fn write_meta_bytes<'a>(
570 &'a self,
571 path: &'a Path,
572 bytes: &'a [u8],
573 ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
574 Box::pin(Self::write_meta_bytes(self, path, bytes))
575 }
576}
577
578#[derive(Clone, Debug, PartialEq, Eq)]
580pub enum AssetSourceEvent {
581 AddedAsset(PathBuf),
583 ModifiedAsset(PathBuf),
585 RemovedAsset(PathBuf),
587 RenamedAsset { old: PathBuf, new: PathBuf },
589 AddedMeta(PathBuf),
591 ModifiedMeta(PathBuf),
593 RemovedMeta(PathBuf),
595 RenamedMeta { old: PathBuf, new: PathBuf },
597 AddedFolder(PathBuf),
599 RemovedFolder(PathBuf),
601 RenamedFolder { old: PathBuf, new: PathBuf },
603 RemovedUnknown {
607 path: PathBuf,
609 is_meta: bool,
613 },
614}
615
616pub trait AssetWatcher: Send + Sync + 'static {}
619
620pub struct VecReader {
622 bytes: Vec<u8>,
623 bytes_read: usize,
624}
625
626impl VecReader {
627 pub fn new(bytes: Vec<u8>) -> Self {
629 Self {
630 bytes_read: 0,
631 bytes,
632 }
633 }
634}
635
636impl AsyncRead for VecReader {
637 fn poll_read(
638 mut self: Pin<&mut Self>,
639 cx: &mut Context<'_>,
640 buf: &mut [u8],
641 ) -> Poll<futures_io::Result<usize>> {
642 if self.bytes_read >= self.bytes.len() {
643 Poll::Ready(Ok(0))
644 } else {
645 let n = ready!(Pin::new(&mut &self.bytes[self.bytes_read..]).poll_read(cx, buf))?;
646 self.bytes_read += n;
647 Poll::Ready(Ok(n))
648 }
649 }
650}
651
652impl AsyncSeekForward for VecReader {
653 fn poll_seek_forward(
654 mut self: Pin<&mut Self>,
655 _cx: &mut Context<'_>,
656 offset: u64,
657 ) -> Poll<std::io::Result<u64>> {
658 let result = self
659 .bytes_read
660 .try_into()
661 .map(|bytes_read: u64| bytes_read + offset);
662
663 if let Ok(new_pos) = result {
664 self.bytes_read = new_pos as _;
665 Poll::Ready(Ok(new_pos as _))
666 } else {
667 Poll::Ready(Err(std::io::Error::new(
668 std::io::ErrorKind::InvalidInput,
669 "seek position is out of range",
670 )))
671 }
672 }
673}
674
675impl Reader for VecReader {
676 fn read_to_end<'a>(
677 &'a mut self,
678 buf: &'a mut Vec<u8>,
679 ) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
680 StackFuture::from(async {
681 if self.bytes_read >= self.bytes.len() {
682 Ok(0)
683 } else {
684 buf.extend_from_slice(&self.bytes[self.bytes_read..]);
685 let n = self.bytes.len() - self.bytes_read;
686 self.bytes_read = self.bytes.len();
687 Ok(n)
688 }
689 })
690 }
691}
692
693pub struct SliceReader<'a> {
695 bytes: &'a [u8],
696 bytes_read: usize,
697}
698
699impl<'a> SliceReader<'a> {
700 pub fn new(bytes: &'a [u8]) -> Self {
702 Self {
703 bytes,
704 bytes_read: 0,
705 }
706 }
707}
708
709impl<'a> AsyncRead for SliceReader<'a> {
710 fn poll_read(
711 mut self: Pin<&mut Self>,
712 cx: &mut Context<'_>,
713 buf: &mut [u8],
714 ) -> Poll<std::io::Result<usize>> {
715 if self.bytes_read >= self.bytes.len() {
716 Poll::Ready(Ok(0))
717 } else {
718 let n = ready!(Pin::new(&mut &self.bytes[self.bytes_read..]).poll_read(cx, buf))?;
719 self.bytes_read += n;
720 Poll::Ready(Ok(n))
721 }
722 }
723}
724
725impl<'a> AsyncSeekForward for SliceReader<'a> {
726 fn poll_seek_forward(
727 mut self: Pin<&mut Self>,
728 _cx: &mut Context<'_>,
729 offset: u64,
730 ) -> Poll<std::io::Result<u64>> {
731 let result = self
732 .bytes_read
733 .try_into()
734 .map(|bytes_read: u64| bytes_read + offset);
735
736 if let Ok(new_pos) = result {
737 self.bytes_read = new_pos as _;
738
739 Poll::Ready(Ok(new_pos as _))
740 } else {
741 Poll::Ready(Err(std::io::Error::new(
742 std::io::ErrorKind::InvalidInput,
743 "seek position is out of range",
744 )))
745 }
746 }
747}
748
749impl Reader for SliceReader<'_> {
750 fn read_to_end<'a>(
751 &'a mut self,
752 buf: &'a mut Vec<u8>,
753 ) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
754 StackFuture::from(async {
755 if self.bytes_read >= self.bytes.len() {
756 Ok(0)
757 } else {
758 buf.extend_from_slice(&self.bytes[self.bytes_read..]);
759 let n = self.bytes.len() - self.bytes_read;
760 self.bytes_read = self.bytes.len();
761 Ok(n)
762 }
763 })
764 }
765}
766
767pub(crate) fn get_meta_path(path: &Path) -> PathBuf {
769 let mut meta_path = path.to_path_buf();
770 let mut extension = path.extension().unwrap_or_default().to_os_string();
771 extension.push(".meta");
772 meta_path.set_extension(extension);
773 meta_path
774}
775
776#[cfg(any(target_arch = "wasm32", target_os = "android"))]
777struct EmptyPathStream;
779
780#[cfg(any(target_arch = "wasm32", target_os = "android"))]
781impl Stream for EmptyPathStream {
782 type Item = PathBuf;
783
784 fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
785 Poll::Ready(None)
786 }
787}