Skip to content

Commit

Permalink
u8 asset crate
Browse files Browse the repository at this point in the history
  • Loading branch information
MalekiRe committed Oct 2, 2024
1 parent 3860057 commit 1d7122a
Show file tree
Hide file tree
Showing 6 changed files with 366 additions and 6 deletions.
36 changes: 36 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ workspace = true

[workspace]
resolver = "2"
members = ["crates/bevy_suis_lasers", "crates/dummy"]
members = ["crates/bevy_suis_lasers", "crates/bevy_u8_assets", "crates/dummy"]

# These settings will apply to all members of the workspace that opt in to them
[workspace.package]
Expand Down
18 changes: 18 additions & 0 deletions crates/bevy_u8_assets/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "bevy_u8_assets"
version = "0.1.0"
edition.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true

[dependencies]
bevy = "0.14.2"
futures-io = "0.3"
thiserror = "1.0.64"
flume = "0.11.0"
futures-lite = "2.0.0"
futures-util = "0.3.30"
futures-locks = "0.7.1"
[lints]
workspace = true
31 changes: 31 additions & 0 deletions crates/bevy_u8_assets/examples/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use bevy::asset::AssetMetaCheck;
use bevy::prelude::*;
use bevy_u8_assets::{U8AssetPlugin, U8AssetWriter};

pub fn main() {
App::new()
.add_plugins(U8AssetPlugin)
.add_plugins(DefaultPlugins.set(AssetPlugin {
meta_check: AssetMetaCheck::Never,
..AssetPlugin::default()
}))
.add_systems(Startup, setup)
.run();
}

fn setup(
mut commands: Commands,
mut writer: ResMut<U8AssetWriter>,
asset_server: Res<AssetServer>,
) {
let bytes =
std::fs::read("/home/malek/RustroverProjects/nexus-vr/assets/Grass_01.png")
.unwrap();
writer.write("grass.png", &[]);
commands.spawn(Camera2dBundle::default());
commands.spawn(SpriteBundle {
texture: asset_server.load("u8://grass.png"),
..default()
});
writer.write_all("grass.png", bytes);
}
214 changes: 214 additions & 0 deletions crates/bevy_u8_assets/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
use bevy::asset::io::ErasedAssetReader;
use bevy::asset::io::{
AssetReader, AssetReaderError, AssetSource, AssetSourceId, PathStream, Reader,
};
pub use bevy::prelude::*;
use flume::{Receiver, Sender};
use futures_io::{AsyncRead, AsyncSeek};
use futures_locks::RwLock;
use std::collections::HashMap;
use std::io::{Cursor, Read};
use std::path::{Path, PathBuf};
use std::pin::Pin;
use std::task::Poll;
use thiserror::Error;

pub struct U8AssetPlugin;

impl Plugin for U8AssetPlugin {
fn build(&self, app: &mut App) {
let (asset_registry, asset_writer) = U8AssetRegistry::new();
app.register_asset_source(
AssetSourceId::new(Some("u8")),
AssetSource::build().with_reader(move || Box::new(asset_registry.clone())),
);
app.insert_resource(asset_writer);
}
}

// Enum to represent the state of asset data
enum AssetDataState {
Pending,
Ready(Vec<u8>),
Done,
}

// Enum to hold either a Receiver or a Vec<u8>
enum AssetSourceEnum {
Stream(Receiver<AssetDataState>),
Data(Vec<u8>),
}

#[derive(Clone)]
pub struct U8AssetRegistry {
rx: Receiver<(PathBuf, Receiver<AssetDataState>)>,
assets: RwLock<HashMap<PathBuf, AssetSourceEnum>>,
}

#[derive(Resource)]
pub struct U8AssetWriter {
writers: HashMap<PathBuf, Sender<AssetDataState>>,
tx: Sender<(PathBuf, Receiver<AssetDataState>)>,
}

impl U8AssetRegistry {
pub fn new() -> (Self, U8AssetWriter) {
let (tx, rx) = flume::bounded(100);
(
Self {
rx,
assets: RwLock::new(HashMap::new()),
},
U8AssetWriter {
writers: HashMap::new(),
tx,
},
)
}

pub async fn add_stream(
&mut self,
path: PathBuf,
receiver: Receiver<AssetDataState>,
) {
self.assets
.write()
.await
.insert(path, AssetSourceEnum::Stream(receiver));
}

pub async fn add_data(&mut self, path: PathBuf, data: Vec<u8>) {
self.assets
.write()
.await
.insert(path, AssetSourceEnum::Data(data));
}
}

impl U8AssetWriter {
fn send(&mut self, path: impl Into<PathBuf>, asset_data_state: AssetDataState) {
let path = path.into();
if !self.writers.contains_key(&path) {
let (tx, rx) = flume::unbounded();
self.tx.send((path.clone(), rx)).unwrap();
self.writers.insert(path.clone(), tx);
}
self.writers
.get_mut(&path)
.unwrap()
.send(asset_data_state)
.unwrap();
}
pub fn write(&mut self, path: impl Into<PathBuf>, data: impl AsRef<[u8]>) {
self.send(path, AssetDataState::Ready(data.as_ref().to_vec()));
}
pub fn finish(&mut self, path: impl Into<PathBuf>) {
self.send(path, AssetDataState::Done);
}
pub fn write_all(&mut self, path: impl Into<PathBuf>, data: impl AsRef<[u8]>) {
let path = path.into();
self.write(&path, data);
self.finish(path);
}
}

#[derive(Error, Debug)]
enum U8AssetReaderError {
#[error("Seek is not supported when embeded")]
SeekNotSupported,
}

#[derive(Default, Debug, Clone)]
pub struct DataReader(pub Cursor<Vec<u8>>);

impl AsyncRead for DataReader {
fn poll_read(
mut self: Pin<&mut Self>,
_: &mut std::task::Context<'_>,
buf: &mut [u8],
) -> Poll<futures_io::Result<usize>> {
use std::io::Read;
let read = self.as_mut().0.read(buf);
Poll::Ready(read)
}
}

impl AsyncSeek for DataReader {
fn poll_seek(
self: Pin<&mut Self>,
_: &mut std::task::Context<'_>,
_pos: futures_io::SeekFrom,
) -> Poll<futures_io::Result<u64>> {
Poll::Ready(Err(futures_io::Error::new(
futures_io::ErrorKind::Other,
U8AssetReaderError::SeekNotSupported,
)))
}
}

impl AssetReader for U8AssetRegistry {
async fn read<'a>(
&'a self,
path: &'a Path,
) -> Result<Box<Reader<'a>>, AssetReaderError> {
for thing in self.rx.try_recv() {
self.assets
.write()
.await
.insert(thing.0, AssetSourceEnum::Stream(thing.1));
}
let data = match self.assets.read().await.get(path) {
Some(AssetSourceEnum::Data(data)) => {
return Ok(Box::new(DataReader(Cursor::new(data.clone()))))
}
Some(AssetSourceEnum::Stream(receiver)) => {
let mut receiver = receiver.clone();
let mut final_data = Vec::new();

loop {
if let Ok(state) = receiver.recv_async().await {
match state {
AssetDataState::Pending => continue,
AssetDataState::Ready(data) => final_data.extend(data),
AssetDataState::Done => break,
}
}
}

if final_data.is_empty() {
return Err(AssetReaderError::NotFound(path.to_path_buf()));
} else {
final_data
}
}
None => return Err(AssetReaderError::NotFound(path.to_path_buf())),
};

self.assets
.write()
.await
.insert(path.to_path_buf(), AssetSourceEnum::Data(data.clone()));
Ok(Box::new(DataReader(Cursor::new(data))))
}

async fn read_meta<'a>(
&'a self,
path: &'a Path,
) -> Result<Box<Reader<'a>>, AssetReaderError> {
Err(AssetReaderError::NotFound(path.to_path_buf()))
}

async fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> Result<Box<PathStream>, AssetReaderError> {
Err(AssetReaderError::NotFound(path.to_path_buf()))
}

async fn is_directory<'a>(
&'a self,
path: &'a Path,
) -> Result<bool, AssetReaderError> {
Ok(false)
}
}
Loading

0 comments on commit 1d7122a

Please sign in to comment.