-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support fast path reads for mmapped/borrowed data
Often times an AssetLoader receives a AsyncRead implementation that is backed by a memory map or a byte slice. In cases where we're dealing with a format that has a zero-copy parser this introduces an unnecessary copy. We avoid this by special casing the "vfs" and "dvdbnd" asset sources to produce a special `FastPathAssetReader`.
- Loading branch information
1 parent
a2d8de6
commit b897920
Showing
9 changed files
with
255 additions
and
89 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
use std::{ | ||
error::Error, | ||
future::Future, | ||
io, | ||
io::Read, | ||
marker::PhantomData, | ||
pin::{pin, Pin}, | ||
task::Poll, | ||
}; | ||
|
||
use bevy::{ | ||
asset::{ | ||
io::{AssetSourceId, Reader}, | ||
meta::Settings, | ||
Asset, AssetLoader, BoxedFuture, LoadContext, | ||
}, | ||
reflect::erased_serde::__private::serde::{Deserialize, Serialize}, | ||
}; | ||
use futures_lite::{AsyncRead, AsyncReadExt}; | ||
use memmap2::Mmap; | ||
|
||
pub trait FastPathAsset: Asset + Sized { | ||
/// The settings type used by this [`AssetLoader`]. | ||
type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>; | ||
|
||
/// The type of [error](`std::error::Error`) which could be encountered by this loader. | ||
type Error: Into<Box<dyn Error + Send + Sync + 'static>> + From<io::Error>; | ||
|
||
fn load_from_bytes<'a>( | ||
reader: &'a [u8], | ||
settings: &'a Self::Settings, | ||
load_context: &'a mut LoadContext, | ||
) -> impl Future<Output = Result<Self, Self::Error>> + Send; | ||
} | ||
|
||
pub struct FastPathAssetLoader<T: FastPathAsset> { | ||
_phantom: PhantomData<T>, | ||
extensions: &'static [&'static str], | ||
} | ||
|
||
impl<T: FastPathAsset> FastPathAssetLoader<T> { | ||
pub fn new(extensions: &'static [&'static str]) -> Self { | ||
Self { | ||
_phantom: PhantomData, | ||
extensions, | ||
} | ||
} | ||
} | ||
|
||
impl<T: FastPathAsset> AssetLoader for FastPathAssetLoader<T> { | ||
type Asset = T; | ||
type Settings = T::Settings; | ||
type Error = T::Error; | ||
|
||
fn load<'a>( | ||
&'a self, | ||
reader: &'a mut Reader, | ||
settings: &'a Self::Settings, | ||
load_context: &'a mut LoadContext, | ||
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> { | ||
Box::pin(async move { | ||
let dvdbnd_asset_source_id: AssetSourceId = AssetSourceId::from("dvdbnd"); | ||
let vfs_asset_source_id: AssetSourceId = AssetSourceId::from("vfs"); | ||
|
||
let source = load_context.asset_path().source(); | ||
let data = if source == &dvdbnd_asset_source_id || source == &vfs_asset_source_id { | ||
// SAFETY: This invariant is upheld by the `dvdbnd` and `vfs` asset source | ||
// implementations. They MUST return an implementation of FastPathReader. | ||
let reader = unsafe { (reader as *mut Reader).cast::<FastPathReader>().as_mut() }; | ||
reader.and_then(|r| r.as_bytes()) | ||
} else { | ||
None | ||
}; | ||
|
||
match data { | ||
None => { | ||
let mut buffer = Vec::new(); | ||
reader.read_to_end(&mut buffer).await?; | ||
|
||
T::load_from_bytes(&buffer, settings, load_context).await | ||
} | ||
Some(slice) => T::load_from_bytes(slice, settings, load_context).await, | ||
} | ||
}) | ||
} | ||
|
||
fn extensions(&self) -> &[&str] { | ||
self.extensions | ||
} | ||
} | ||
|
||
/// An [`AsyncRead`] implementation that allows consuming Bevy asset loaders to bypass the read | ||
/// implementation and directly access the data when available. | ||
pub enum FastPathReader<'a> { | ||
MemoryMapped(Mmap, usize), | ||
Reader(Box<dyn AsyncRead + Unpin + Send + Sync + 'a>), | ||
Slice(&'a [u8]), | ||
} | ||
|
||
impl<'a> FastPathReader<'a> { | ||
pub fn as_bytes(&'a self) -> Option<&'a [u8]> { | ||
match self { | ||
FastPathReader::Slice(slice) => Some(slice), | ||
FastPathReader::MemoryMapped(mmap, _) => Some(&mmap[..]), | ||
FastPathReader::Reader(_) => None, | ||
} | ||
} | ||
} | ||
|
||
impl<'a> AsyncRead for FastPathReader<'a> { | ||
fn poll_read( | ||
self: Pin<&mut Self>, | ||
_cx: &mut std::task::Context<'_>, | ||
buf: &mut [u8], | ||
) -> Poll<io::Result<usize>> { | ||
match self.get_mut() { | ||
FastPathReader::Reader(reader) => AsyncRead::poll_read(pin!(reader), _cx, buf), | ||
FastPathReader::Slice(slice) => Poll::Ready(Read::read(slice, buf)), | ||
FastPathReader::MemoryMapped(dvd_bnd, ref mut offset) => { | ||
let mut data = &dvd_bnd[*offset..]; | ||
let read = match Read::read(&mut data, buf) { | ||
Ok(length) => length, | ||
Err(e) => return Poll::Ready(Err(e)), | ||
}; | ||
|
||
*offset += read; | ||
|
||
Poll::Ready(Ok(read)) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.