Skip to content

Commit f08f077

Browse files
authored
Replace AsyncSeek trait by AsyncSeekForward for Reader to address #12880 (#14194)
# Objective The primary motivation behind this PR is to (partially?) address the limitations imposed by the recently added `AsyncSeek` trait bound discussed in issue #12880. While the `AsyncSeek` trait add some flexibility to the reader, it inadvertently restricts the ability to write asset readers that can truly stream bytes, particularly in scenarios like HTTP requests where backward seeking is not supported. It is also challenging in contexts where assets are stored in compressed formats or require other kinds of transformations. The logic behind this change is that currently, with `AsyncSeek`, an asset Reader based on streamed data will either 1) fail silently, 2) return an error, or 3) use a buffer to satisfy the trait constraint. I believe that being able to advance in the file without having to "read" it is a good thing. The only issue here is the ability to seek backward. It is highly likely that in this context, we only need to seek forward in the file because we would have already read an entry table upstream and just want to access one or more resources further in the file. I understand that in some cases, this may not be applicable, but I think it is more beneficial not to constrain `Reader`s that want to stream than to allow "Assets" to read files in a completely arbitrary order. ## Solution Replace the current `AsyncSeek` trait with `AsyncSeekForward` on asset `Reader` ## Changelog - Introduced a new custom trait, `AsyncSeekForward`, for the asset Reader. - Replaced the current `AsyncSeek` trait with `AsyncSeekForward` for all asset `Reader` implementations. ## Migration Guide Replace all instances of `AsyncSeek` with `AsyncSeekForward` in your asset reader implementations.
1 parent de888a3 commit f08f077

File tree

5 files changed

+113
-96
lines changed

5 files changed

+113
-96
lines changed

crates/bevy_asset/src/io/file/file_asset.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,35 @@
11
use crate::io::{
2-
get_meta_path, AssetReader, AssetReaderError, AssetWriter, AssetWriterError, PathStream,
3-
Reader, Writer,
2+
get_meta_path, AssetReader, AssetReaderError, AssetWriter, AssetWriterError, AsyncSeekForward,
3+
PathStream, Reader, Writer,
44
};
55
use async_fs::{read_dir, File};
6+
use futures_io::AsyncSeek;
67
use futures_lite::StreamExt;
78

9+
use core::{pin::Pin, task, task::Poll};
810
use std::path::Path;
911

1012
use super::{FileAssetReader, FileAssetWriter};
1113

14+
impl AsyncSeekForward for File {
15+
fn poll_seek_forward(
16+
mut self: Pin<&mut Self>,
17+
cx: &mut task::Context<'_>,
18+
offset: u64,
19+
) -> Poll<futures_io::Result<u64>> {
20+
let offset: Result<i64, _> = offset.try_into();
21+
22+
if let Ok(offset) = offset {
23+
Pin::new(&mut self).poll_seek(cx, futures_io::SeekFrom::Current(offset))
24+
} else {
25+
Poll::Ready(Err(std::io::Error::new(
26+
std::io::ErrorKind::InvalidInput,
27+
"seek position is out of range",
28+
)))
29+
}
30+
}
31+
}
32+
1233
impl Reader for File {}
1334

1435
impl AssetReader for FileAssetReader {

crates/bevy_asset/src/io/file/sync_file_asset.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
use futures_io::{AsyncRead, AsyncSeek, AsyncWrite};
1+
use futures_io::{AsyncRead, AsyncWrite};
22
use futures_lite::Stream;
33

44
use crate::io::{
5-
get_meta_path, AssetReader, AssetReaderError, AssetWriter, AssetWriterError, PathStream,
6-
Reader, Writer,
5+
get_meta_path, AssetReader, AssetReaderError, AssetWriter, AssetWriterError, AsyncSeekForward,
6+
PathStream, Reader, Writer,
77
};
88

99
use core::{pin::Pin, task::Poll};
@@ -29,14 +29,16 @@ impl AsyncRead for FileReader {
2929
}
3030
}
3131

32-
impl AsyncSeek for FileReader {
33-
fn poll_seek(
32+
impl AsyncSeekForward for FileReader {
33+
fn poll_seek_forward(
3434
self: Pin<&mut Self>,
3535
_cx: &mut core::task::Context<'_>,
36-
pos: std::io::SeekFrom,
36+
offset: u64,
3737
) -> Poll<std::io::Result<u64>> {
3838
let this = self.get_mut();
39-
let seek = this.0.seek(pos);
39+
let current = this.0.stream_position()?;
40+
let seek = this.0.seek(std::io::SeekFrom::Start(current + offset));
41+
4042
Poll::Ready(seek)
4143
}
4244
}

crates/bevy_asset/src/io/memory.rs

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@ use crate::io::{AssetReader, AssetReaderError, PathStream, Reader};
22
use alloc::sync::Arc;
33
use bevy_utils::HashMap;
44
use core::{pin::Pin, task::Poll};
5-
use futures_io::{AsyncRead, AsyncSeek};
5+
use futures_io::AsyncRead;
66
use futures_lite::{ready, Stream};
77
use parking_lot::RwLock;
8-
use std::{
9-
io::SeekFrom,
10-
path::{Path, PathBuf},
11-
};
8+
use std::path::{Path, PathBuf};
9+
10+
use super::AsyncSeekForward;
1211

1312
#[derive(Default, Debug)]
1413
struct DirInternal {
@@ -247,37 +246,20 @@ impl AsyncRead for DataReader {
247246
}
248247
}
249248

250-
impl AsyncSeek for DataReader {
251-
fn poll_seek(
249+
impl AsyncSeekForward for DataReader {
250+
fn poll_seek_forward(
252251
mut self: Pin<&mut Self>,
253252
_cx: &mut core::task::Context<'_>,
254-
pos: SeekFrom,
253+
offset: u64,
255254
) -> Poll<std::io::Result<u64>> {
256-
let result = match pos {
257-
SeekFrom::Start(offset) => offset.try_into(),
258-
SeekFrom::End(offset) => self
259-
.data
260-
.value()
261-
.len()
262-
.try_into()
263-
.map(|len: i64| len - offset),
264-
SeekFrom::Current(offset) => self
265-
.bytes_read
266-
.try_into()
267-
.map(|bytes_read: i64| bytes_read + offset),
268-
};
255+
let result = self
256+
.bytes_read
257+
.try_into()
258+
.map(|bytes_read: u64| bytes_read + offset);
269259

270260
if let Ok(new_pos) = result {
271-
if new_pos < 0 {
272-
Poll::Ready(Err(std::io::Error::new(
273-
std::io::ErrorKind::InvalidInput,
274-
"seek position is out of range",
275-
)))
276-
} else {
277-
self.bytes_read = new_pos as _;
278-
279-
Poll::Ready(Ok(new_pos as _))
280-
}
261+
self.bytes_read = new_pos as _;
262+
Poll::Ready(Ok(new_pos as _))
281263
} else {
282264
Poll::Ready(Err(std::io::Error::new(
283265
std::io::ErrorKind::InvalidInput,

crates/bevy_asset/src/io/mod.rs

Lines changed: 60 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,9 @@ use core::{
2828
pin::Pin,
2929
task::{Context, Poll},
3030
};
31-
use futures_io::{AsyncRead, AsyncSeek, AsyncWrite};
31+
use futures_io::{AsyncRead, AsyncWrite};
3232
use futures_lite::{ready, Stream};
33-
use std::{
34-
io::SeekFrom,
35-
path::{Path, PathBuf},
36-
};
33+
use std::path::{Path, PathBuf};
3734
use thiserror::Error;
3835

3936
/// Errors that occur while loading assets.
@@ -83,13 +80,51 @@ pub const STACK_FUTURE_SIZE: usize = 10 * size_of::<&()>();
8380

8481
pub use stackfuture::StackFuture;
8582

83+
/// Asynchronously advances the cursor position by a specified number of bytes.
84+
///
85+
/// This trait is a simplified version of the [`futures_io::AsyncSeek`] trait, providing
86+
/// support exclusively for the [`futures_io::SeekFrom::Current`] variant. It allows for relative
87+
/// seeking from the current cursor position.
88+
pub trait AsyncSeekForward {
89+
/// Attempts to asynchronously seek forward by a specified number of bytes from the current cursor position.
90+
///
91+
/// Seeking beyond the end of the stream is allowed and the behavior for this case is defined by the implementation.
92+
/// The new position, relative to the beginning of the stream, should be returned upon successful completion
93+
/// of the seek operation.
94+
///
95+
/// If the seek operation completes successfully,
96+
/// the new position relative to the beginning of the stream should be returned.
97+
///
98+
/// # Implementation
99+
///
100+
/// Implementations of this trait should handle [`Poll::Pending`] correctly, converting
101+
/// [`std::io::ErrorKind::WouldBlock`] errors into [`Poll::Pending`] to indicate that the operation is not
102+
/// yet complete and should be retried, and either internally retry or convert
103+
/// [`std::io::ErrorKind::Interrupted`] into another error kind.
104+
fn poll_seek_forward(
105+
self: Pin<&mut Self>,
106+
cx: &mut Context<'_>,
107+
offset: u64,
108+
) -> Poll<futures_io::Result<u64>>;
109+
}
110+
111+
impl<T: ?Sized + AsyncSeekForward + Unpin> AsyncSeekForward for Box<T> {
112+
fn poll_seek_forward(
113+
mut self: Pin<&mut Self>,
114+
cx: &mut Context<'_>,
115+
offset: u64,
116+
) -> Poll<futures_io::Result<u64>> {
117+
Pin::new(&mut **self).poll_seek_forward(cx, offset)
118+
}
119+
}
120+
86121
/// A type returned from [`AssetReader::read`], which is used to read the contents of a file
87122
/// (or virtual file) corresponding to an asset.
88123
///
89-
/// This is essentially a trait alias for types implementing [`AsyncRead`] and [`AsyncSeek`].
124+
/// This is essentially a trait alias for types implementing [`AsyncRead`] and [`AsyncSeekForward`].
90125
/// The only reason a blanket implementation is not provided for applicable types is to allow
91126
/// implementors to override the provided implementation of [`Reader::read_to_end`].
92-
pub trait Reader: AsyncRead + AsyncSeek + Unpin + Send + Sync {
127+
pub trait Reader: AsyncRead + AsyncSeekForward + Unpin + Send + Sync {
93128
/// Reads the entire contents of this reader and appends them to a vec.
94129
///
95130
/// # Note for implementors
@@ -559,32 +594,20 @@ impl AsyncRead for VecReader {
559594
}
560595
}
561596

562-
impl AsyncSeek for VecReader {
563-
fn poll_seek(
597+
impl AsyncSeekForward for VecReader {
598+
fn poll_seek_forward(
564599
mut self: Pin<&mut Self>,
565600
_cx: &mut Context<'_>,
566-
pos: SeekFrom,
601+
offset: u64,
567602
) -> Poll<std::io::Result<u64>> {
568-
let result = match pos {
569-
SeekFrom::Start(offset) => offset.try_into(),
570-
SeekFrom::End(offset) => self.bytes.len().try_into().map(|len: i64| len - offset),
571-
SeekFrom::Current(offset) => self
572-
.bytes_read
573-
.try_into()
574-
.map(|bytes_read: i64| bytes_read + offset),
575-
};
603+
let result = self
604+
.bytes_read
605+
.try_into()
606+
.map(|bytes_read: u64| bytes_read + offset);
576607

577608
if let Ok(new_pos) = result {
578-
if new_pos < 0 {
579-
Poll::Ready(Err(std::io::Error::new(
580-
std::io::ErrorKind::InvalidInput,
581-
"seek position is out of range",
582-
)))
583-
} else {
584-
self.bytes_read = new_pos as _;
585-
586-
Poll::Ready(Ok(new_pos as _))
587-
}
609+
self.bytes_read = new_pos as _;
610+
Poll::Ready(Ok(new_pos as _))
588611
} else {
589612
Poll::Ready(Err(std::io::Error::new(
590613
std::io::ErrorKind::InvalidInput,
@@ -644,32 +667,21 @@ impl<'a> AsyncRead for SliceReader<'a> {
644667
}
645668
}
646669

647-
impl<'a> AsyncSeek for SliceReader<'a> {
648-
fn poll_seek(
670+
impl<'a> AsyncSeekForward for SliceReader<'a> {
671+
fn poll_seek_forward(
649672
mut self: Pin<&mut Self>,
650673
_cx: &mut Context<'_>,
651-
pos: SeekFrom,
674+
offset: u64,
652675
) -> Poll<std::io::Result<u64>> {
653-
let result = match pos {
654-
SeekFrom::Start(offset) => offset.try_into(),
655-
SeekFrom::End(offset) => self.bytes.len().try_into().map(|len: i64| len - offset),
656-
SeekFrom::Current(offset) => self
657-
.bytes_read
658-
.try_into()
659-
.map(|bytes_read: i64| bytes_read + offset),
660-
};
676+
let result = self
677+
.bytes_read
678+
.try_into()
679+
.map(|bytes_read: u64| bytes_read + offset);
661680

662681
if let Ok(new_pos) = result {
663-
if new_pos < 0 {
664-
Poll::Ready(Err(std::io::Error::new(
665-
std::io::ErrorKind::InvalidInput,
666-
"seek position is out of range",
667-
)))
668-
} else {
669-
self.bytes_read = new_pos as _;
682+
self.bytes_read = new_pos as _;
670683

671-
Poll::Ready(Ok(new_pos as _))
672-
}
684+
Poll::Ready(Ok(new_pos as _))
673685
} else {
674686
Poll::Ready(Err(std::io::Error::new(
675687
std::io::ErrorKind::InvalidInput,

crates/bevy_asset/src/io/processor_gated.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ use alloc::sync::Arc;
77
use async_lock::RwLockReadGuardArc;
88
use bevy_utils::tracing::trace;
99
use core::{pin::Pin, task::Poll};
10-
use futures_io::{AsyncRead, AsyncSeek};
11-
use std::{io::SeekFrom, path::Path};
10+
use futures_io::AsyncRead;
11+
use std::path::Path;
1212

13-
use super::ErasedAssetReader;
13+
use super::{AsyncSeekForward, ErasedAssetReader};
1414

1515
/// An [`AssetReader`] that will prevent asset (and asset metadata) read futures from returning for a
1616
/// given path until that path has been processed by [`AssetProcessor`].
1717
///
18-
/// [`AssetProcessor`]: crate::processor::AssetProcessor
18+
/// [`AssetProcessor`]: crate::processor::AssetProcessor
1919
pub struct ProcessorGatedReader {
2020
reader: Box<dyn ErasedAssetReader>,
2121
source: AssetSourceId<'static>,
@@ -142,13 +142,13 @@ impl AsyncRead for TransactionLockedReader<'_> {
142142
}
143143
}
144144

145-
impl AsyncSeek for TransactionLockedReader<'_> {
146-
fn poll_seek(
145+
impl AsyncSeekForward for TransactionLockedReader<'_> {
146+
fn poll_seek_forward(
147147
mut self: Pin<&mut Self>,
148148
cx: &mut core::task::Context<'_>,
149-
pos: SeekFrom,
149+
offset: u64,
150150
) -> Poll<std::io::Result<u64>> {
151-
Pin::new(&mut self.reader).poll_seek(cx, pos)
151+
Pin::new(&mut self.reader).poll_seek_forward(cx, offset)
152152
}
153153
}
154154

0 commit comments

Comments
 (0)