Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/bevy_asset/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ bevy_platform = { path = "../bevy_platform", version = "0.18.0-dev", default-fea
"std",
] }

stackfuture = { version = "0.3", default-features = false }
stackfuture = { git = "https://github.com/joseph-gio/stackfuture", branch = "local-stack-future", default-features = false }
atomicow = { version = "1.1", default-features = false, features = ["std"] }
async-broadcast = { version = "0.7.2", default-features = false }
async-fs = { version = "2.0", default-features = false }
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_asset/src/io/file/sync_file_asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use futures_lite::Stream;

use crate::io::{
get_meta_path, AssetReader, AssetReaderError, AssetWriter, AssetWriterError, AsyncSeek,
PathStream, Reader, ReaderRequiredFeatures, Writer,
ConditionalSendStackFuture, PathStream, Reader, ReaderRequiredFeatures, Writer,
};

use alloc::{borrow::ToOwned, boxed::Box, vec::Vec};
Expand Down Expand Up @@ -44,9 +44,9 @@ impl Reader for FileReader {
fn read_to_end<'a>(
&'a mut self,
buf: &'a mut Vec<u8>,
) -> stackfuture::StackFuture<'a, std::io::Result<usize>, { crate::io::STACK_FUTURE_SIZE }>
) -> ConditionalSendStackFuture<'a, std::io::Result<usize>, { crate::io::STACK_FUTURE_SIZE }>
{
stackfuture::StackFuture::from(async { self.0.read_to_end(buf) })
ConditionalSendStackFuture::from(async { self.0.read_to_end(buf) })
}
}

Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_asset/src/io/memory.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::io::{
AssetReader, AssetReaderError, AssetWriter, AssetWriterError, PathStream, Reader,
ReaderRequiredFeatures,
AssetReader, AssetReaderError, AssetWriter, AssetWriterError, ConditionalSendStackFuture,
PathStream, Reader, ReaderRequiredFeatures,
};
use alloc::{borrow::ToOwned, boxed::Box, sync::Arc, vec, vec::Vec};
use bevy_platform::{
Expand Down Expand Up @@ -351,7 +351,7 @@ impl Reader for DataReader {
fn read_to_end<'a>(
&'a mut self,
buf: &'a mut Vec<u8>,
) -> stackfuture::StackFuture<'a, std::io::Result<usize>, { super::STACK_FUTURE_SIZE }> {
) -> ConditionalSendStackFuture<'a, std::io::Result<usize>, { super::STACK_FUTURE_SIZE }> {
crate::io::read_to_end(self.data.value(), &mut self.bytes_read, buf)
}
}
Expand Down
34 changes: 19 additions & 15 deletions crates/bevy_asset/src/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,19 @@ pub mod gated;

mod source;

pub use futures_io::{AsyncRead, AsyncSeek, AsyncWrite, SeekFrom};
pub use futures_lite::AsyncWriteExt;
pub use source::*;

use alloc::{boxed::Box, sync::Arc, vec::Vec};
use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
use bevy_tasks::{BoxedFuture, ConditionalSend, ConditionalSendFuture};
use core::{
mem::size_of,
pin::Pin,
task::{Context, Poll},
};
use futures_io::{AsyncRead, AsyncSeek, AsyncWrite};
use futures_lite::Stream;
use std::{
io::SeekFrom,
path::{Path, PathBuf},
};
use std::path::{Path, PathBuf};
use thiserror::Error;

/// Errors that occur while loading assets.
Expand Down Expand Up @@ -131,7 +128,14 @@ pub enum SeekKind {
// a higher maximum necessary.
pub const STACK_FUTURE_SIZE: usize = 10 * size_of::<&()>();

pub use stackfuture::StackFuture;
pub use stackfuture::{LocalStackFuture, StackFuture};

#[cfg(target_arch = "wasm32")]
pub type ConditionalSendStackFuture<'a, T, const STACK_SIZE: usize> =
LocalStackFuture<'a, T, STACK_SIZE>;
#[cfg(not(target_arch = "wasm32"))]
pub type ConditionalSendStackFuture<'a, T, const STACK_SIZE: usize> =
StackFuture<'a, T, STACK_SIZE>;

/// A type returned from [`AssetReader::read`], which is used to read the contents of a file
/// (or virtual file) corresponding to an asset.
Expand All @@ -154,7 +158,7 @@ pub use stackfuture::StackFuture;
/// [`SeekKind::AnySeek`] to indicate that they may seek backward, or from the start/end. A reader
/// implementation may choose to support that, or may just detect those kinds of seeks and return an
/// error.
pub trait Reader: AsyncRead + AsyncSeek + Unpin + Send + Sync {
pub trait Reader: AsyncRead + AsyncSeek + Unpin + ConditionalSend {
/// Reads the entire contents of this reader and appends them to a vec.
///
/// # Note for implementors
Expand All @@ -164,17 +168,17 @@ pub trait Reader: AsyncRead + AsyncSeek + Unpin + Send + Sync {
fn read_to_end<'a>(
&'a mut self,
buf: &'a mut Vec<u8>,
) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
) -> ConditionalSendStackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
let future = futures_lite::AsyncReadExt::read_to_end(self, buf);
StackFuture::from(future)
ConditionalSendStackFuture::from(future)
}
}

impl Reader for Box<dyn Reader + '_> {
fn read_to_end<'a>(
&'a mut self,
buf: &'a mut Vec<u8>,
) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
) -> ConditionalSendStackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
(**self).read_to_end(buf)
}
}
Expand Down Expand Up @@ -682,7 +686,7 @@ impl Reader for VecReader {
fn read_to_end<'a>(
&'a mut self,
buf: &'a mut Vec<u8>,
) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
) -> ConditionalSendStackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
read_to_end(&self.bytes, &mut self.bytes_read, buf)
}
}
Expand Down Expand Up @@ -727,7 +731,7 @@ impl Reader for SliceReader<'_> {
fn read_to_end<'a>(
&'a mut self,
buf: &'a mut Vec<u8>,
) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
) -> ConditionalSendStackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
read_to_end(self.bytes, &mut self.bytes_read, buf)
}
}
Expand Down Expand Up @@ -781,8 +785,8 @@ pub(crate) fn read_to_end<'a>(
source: &'a [u8],
bytes_read: &'a mut usize,
dest: &'a mut Vec<u8>,
) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
StackFuture::from(async {
) -> ConditionalSendStackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
ConditionalSendStackFuture::from(async {
if *bytes_read >= source.len() {
Ok(0)
} else {
Expand Down
5 changes: 3 additions & 2 deletions crates/bevy_asset/src/io/processor_gated.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{
io::{
AssetReader, AssetReaderError, AssetSourceId, PathStream, Reader, ReaderRequiredFeatures,
AssetReader, AssetReaderError, AssetSourceId, ConditionalSendStackFuture, PathStream,
Reader, ReaderRequiredFeatures,
},
processor::{ProcessStatus, ProcessingState},
AssetPath,
Expand Down Expand Up @@ -155,7 +156,7 @@ impl Reader for TransactionLockedReader<'_> {
fn read_to_end<'a>(
&'a mut self,
buf: &'a mut Vec<u8>,
) -> stackfuture::StackFuture<'a, std::io::Result<usize>, { super::STACK_FUTURE_SIZE }> {
) -> ConditionalSendStackFuture<'a, std::io::Result<usize>, { super::STACK_FUTURE_SIZE }> {
self.reader.read_to_end(buf)
}
}
118 changes: 113 additions & 5 deletions crates/bevy_asset/src/io/wasm.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use crate::io::{
get_meta_path, AssetReader, AssetReaderError, EmptyPathStream, PathStream, Reader,
ReaderRequiredFeatures, VecReader,
get_meta_path, AssetReader, AssetReaderError, AsyncRead, AsyncSeek, EmptyPathStream,
LocalStackFuture, PathStream, Reader, ReaderRequiredFeatures, SeekFrom, STACK_FUTURE_SIZE,
};
use alloc::{borrow::ToOwned, boxed::Box, format};
use alloc::{borrow::ToOwned, boxed::Box, format, vec::Vec};
use core::pin::Pin;
use core::task::{Context, Poll};
use js_sys::{Uint8Array, JSON};
use std::path::{Path, PathBuf};
use tracing::error;
Expand Down Expand Up @@ -79,8 +81,8 @@ impl HttpWasmAssetReader {
match resp.status() {
200 => {
let data = JsFuture::from(resp.array_buffer().unwrap()).await.unwrap();
let bytes = Uint8Array::new(&data).to_vec();
let reader = VecReader::new(bytes);
let bytes = Uint8Array::new(&data);
let reader = Uint8ArrayReader::new(bytes);
Ok(reader)
}
// Some web servers, including itch.io's CDN, return 403 when a requested file isn't present.
Expand Down Expand Up @@ -121,3 +123,109 @@ impl AssetReader for HttpWasmAssetReader {
Ok(false)
}
}

/// An [`AsyncRead`] implementation capable of reading a [`Uint8Array`].
pub struct Uint8ArrayReader {
array: Uint8Array,
initial_offset: u32,
}

impl Uint8ArrayReader {
/// Create a new [`Uint8ArrayReader`] for `array`.
pub fn new(array: Uint8Array) -> Self {
Self {
initial_offset: array.byte_offset(),
array,
}
}
}

impl AsyncRead for Uint8ArrayReader {
fn poll_read(
mut self: Pin<&mut Self>,
_cx: &mut Context,
buf: &mut [u8],
) -> Poll<futures_io::Result<usize>> {
let array_len = self.array.length();
let n = u32::min(buf.len() as u32, array_len);
self.array.subarray(0, n).copy_to(&mut buf[..n as usize]); // NOTE: copy_to will panic if the lengths do not exactly match
self.array = self.array.subarray(n, array_len);
Poll::Ready(Ok(n as usize))
}
}

impl AsyncSeek for Uint8ArrayReader {
fn poll_seek(
mut self: Pin<&mut Self>,
_cx: &mut Context,
seek_from: SeekFrom,
) -> Poll<std::io::Result<u64>> {
let array_len = self.array.length();
let current_array_buffer_offset = self.array.byte_offset();
let array_buffer_end = current_array_buffer_offset + array_len;
let new_array_buffer_offset = match seek_from {
SeekFrom::Start(from_start) => self
.initial_offset
.saturating_add(u32::try_from(from_start).unwrap_or(u32::MAX))
.min(array_buffer_end),
SeekFrom::End(from_end) => {
if from_end.is_negative() {
array_buffer_end
.saturating_sub(u32::try_from(from_end.abs()).unwrap_or(u32::MAX))
.max(self.initial_offset)
} else {
array_buffer_end
}
}
SeekFrom::Current(from_current) => {
if from_current.is_negative() {
current_array_buffer_offset
.saturating_sub(u32::try_from(from_current.abs()).unwrap_or(u32::MAX))
.max(self.initial_offset)
} else {
current_array_buffer_offset
.saturating_add(u32::try_from(from_current).unwrap_or(u32::MAX))
.min(array_buffer_end)
}
}
};
debug_assert!(new_array_buffer_offset >= self.initial_offset);
debug_assert!(new_array_buffer_offset <= array_buffer_end);
self.array = Uint8Array::new_with_byte_offset_and_length(
self.array.buffer().unchecked_ref(),
new_array_buffer_offset,
array_buffer_end - new_array_buffer_offset,
);
Poll::Ready(Ok((new_array_buffer_offset - self.initial_offset).into()))
}
}

impl Reader for Uint8ArrayReader {
fn read_to_end<'a>(
&'a mut self,
buf: &'a mut Vec<u8>,
) -> LocalStackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
#[expect(unsafe_code)]
LocalStackFuture::from(async {
let n = self.array.length();
let n_usize = n as usize;

buf.reserve_exact(n_usize);
let spare_capacity = buf.spare_capacity_mut();
debug_assert!(spare_capacity.len() >= n_usize);
// NOTE: `copy_to_uninit` requires the lengths to match exactly,
// and `reserve_exact` may reserve more capacity than required.
self.array.copy_to_uninit(&mut spare_capacity[..n_usize]);
// SAFETY:
// * the vector has enough spare capacity for `n` additional bytes due to `reserve_exact` above
// * the bytes have been initialized due to `copy_to_uninit` above.
unsafe {
let new_len = buf.len() + n_usize;
buf.set_len(new_len);
}
self.array = self.array.subarray(n, n);

Ok(n_usize)
})
}
}
Loading