Skip to content

Commit

Permalink
add postcard format
Browse files Browse the repository at this point in the history
  • Loading branch information
boylede committed Feb 11, 2024
1 parent 18c17fb commit d5f7d6e
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 1 deletion.
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ json = ["dep:serde_json"]
msgpack = ["dep:rmp-serde"]
xml = ["dep:quick-xml"]
csv = ["dep:csv"]
postcard = ["dep:postcard"]

[dependencies]
bevy = { version = "0.12", default-features = false, features = ["bevy_asset"] }
Expand All @@ -33,6 +34,7 @@ thiserror = "1.0"
quick-xml = { version = "0.31", features = [ "serialize" ], optional = true }
serde = { version = "1" }
anyhow = { version = "1" }
postcard = {version = "1.0", features = ["use-std"], optional = true}

[dev-dependencies]
bevy = { version = "0.12" }
Expand All @@ -47,6 +49,11 @@ name = "msgpack"
path = "examples/msgpack.rs"
required-features = ["msgpack"]

[[example]]
name = "postcard"
path = "examples/postcard.rs"
required-features = ["postcard"]

[[example]]
name = "ron"
path = "examples/ron.rs"
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Supported formats:
|:-----------|:-----------|:---------------------------------------|
| `json` | `json` | [`json.rs`](./examples/json.rs) |
| `msgpack` | `msgpack` | [`msgpack.rs`](./examples/msgpack.rs) |
| `postcard`| `postcard` | [`postcard.rs`](./examples/postcard.rs)|
| `ron` | `ron` | [`ron.rs`](./examples/ron.rs) |
| `toml` | `toml` | [`toml.rs`](./examples/toml.rs) |
| `xml` | `xml` | [`xml.rs`](./examples/xml.rs) |
Expand All @@ -37,6 +38,7 @@ as a generic parameter. You also need to configure custom file endings for each
use bevy::prelude::*;
use bevy_common_assets::json::JsonAssetPlugin;
use bevy_common_assets::msgpack::MsgPackAssetPlugin;
use bevy_common_assets::ron::PostcardAssetPlugin;
use bevy_common_assets::ron::RonAssetPlugin;
use bevy_common_assets::toml::TomlAssetPlugin;
use bevy_common_assets::xml::XmlAssetPlugin;
Expand All @@ -49,6 +51,7 @@ fn main() {
JsonAssetPlugin::<Level>::new(&["level.json", "custom.json"]),
RonAssetPlugin::<Level>::new(&["level.ron"]),
MsgPackAssetPlugin::<Level>::new(&["level.msgpack"]),
PostcardAssetPlugin::<Level>::new(&["level.postcard"]),
TomlAssetPlugin::<Level>::new(&["level.toml"]),
XmlAssetPlugin::<Level>::new(&["level.xml"]),
YamlAssetPlugin::<Level>::new(&["level.yaml"])
Expand Down
Binary file added assets/trees.level.postcard
Binary file not shown.
61 changes: 61 additions & 0 deletions examples/postcard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use bevy::prelude::*;
use bevy::reflect::TypePath;
use bevy_common_assets::postcard::PostcardAssetPlugin;

fn main() {
App::new()
.add_plugins((
DefaultPlugins,
PostcardAssetPlugin::<Level>::new(&["level.postcard"]),
))
.insert_resource(Msaa::Off)
.add_state::<AppState>()
.add_systems(Startup, setup)
.add_systems(Update, spawn_level.run_if(in_state(AppState::Loading)))
.run()
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
let level = LevelHandle(asset_server.load("trees.level.postcard"));
commands.insert_resource(level);
let tree = ImageHandle(asset_server.load("tree.png"));
commands.insert_resource(tree);
commands.spawn(Camera2dBundle::default());
}

fn spawn_level(
mut commands: Commands,
level: Res<LevelHandle>,
tree: Res<ImageHandle>,
mut levels: ResMut<Assets<Level>>,
mut state: ResMut<NextState<AppState>>,
) {
if let Some(level) = levels.remove(level.0.id()) {
for position in level.positions {
commands.spawn(SpriteBundle {
transform: Transform::from_translation(position.into()),
texture: tree.0.clone(),
..default()
});
}
state.set(AppState::Level);
}
}

#[derive(serde::Deserialize, Asset, TypePath)]
struct Level {
positions: Vec<[f32; 3]>,
}

#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, States)]
enum AppState {
#[default]
Loading,
Level,
}

#[derive(Resource)]
struct ImageHandle(Handle<Image>);

#[derive(Resource)]
struct LevelHandle(Handle<Level>);
7 changes: 6 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ pub mod json;
#[cfg_attr(docsrs, doc(cfg(feature = "msgpack")))]
#[cfg(feature = "msgpack")]
pub mod msgpack;
/// Module containing a Bevy plugin to load assets from `postcard` files with custom file extensions.
#[cfg_attr(docsrs, doc(cfg(feature = "postcard")))]
#[cfg(feature = "postcard")]
pub mod postcard;
/// Module containing a Bevy plugin to load assets from `ron` files with custom file extensions.
#[cfg_attr(docsrs, doc(cfg(feature = "ron")))]
#[cfg(feature = "ron")]
Expand All @@ -84,7 +88,8 @@ pub mod yaml;
feature = "toml",
feature = "xml",
feature = "yaml",
feature = "csv"
feature = "csv",
feature = "postcard",
))]
#[doc = include_str!("../README.md")]
#[cfg(doctest)]
Expand Down
113 changes: 113 additions & 0 deletions src/postcard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use bevy::{
app::{App, Plugin},
asset::{
io::Reader, saver::AssetSaver, Asset, AssetApp, AssetLoader, AsyncReadExt, AsyncWriteExt,
LoadContext,
},
prelude::*,
utils::{thiserror, BoxedFuture},
};
use postcard::{from_bytes, to_stdvec};
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
use thiserror::Error;

/// Plugin to load your asset type `A` from `Postcard` files.
pub struct PostcardAssetPlugin<A> {
extensions: Vec<&'static str>,
_marker: PhantomData<A>,
}

impl<A> Plugin for PostcardAssetPlugin<A>
where
for<'de> A: Deserialize<'de> + Asset,
{
fn build(&self, app: &mut App) {
app.init_asset::<A>()
.register_asset_loader(PostcardAssetLoader::<A> {
extensions: self.extensions.clone(),
_marker: PhantomData,
});
}
}

impl<A> PostcardAssetPlugin<A>
where
for<'de> A: Deserialize<'de> + Asset,
{
/// Create a new plugin that will load assets from files with the given extensions.
pub fn new(extensions: &[&'static str]) -> Self {
Self {
extensions: extensions.to_owned(),
_marker: PhantomData,
}
}
}

struct PostcardAssetLoader<A> {
extensions: Vec<&'static str>,
_marker: PhantomData<A>,
}

/// Possible errors that can be produced by [`PostcardAssetLoader`] or [`PostcardAssetSaver`]
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum PostcardAssetError {
/// An [IO Error](std::io::Error)
#[error("Could not read the file: {0}")]
Io(#[from] std::io::Error),
/// A [Postcard Error](postcard::Error)
#[error("Could not parse Postcard: {0}")]
PostcardError(#[from] postcard::Error),
}

impl<A> AssetLoader for PostcardAssetLoader<A>
where
for<'de> A: Deserialize<'de> + Asset,
{
type Asset = A;
type Settings = ();
type Error = PostcardAssetError;

fn load<'a>(
&'a self,
reader: &'a mut Reader,
_settings: &'a (),
_load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> {
Box::pin(async move {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let asset = from_bytes::<A>(&bytes)?;
Ok(asset)
})
}

fn extensions(&self) -> &[&str] {
&self.extensions
}
}

struct PostcardAssetSaver<A> {
_marker: PhantomData<A>,
}

impl<A: Asset + Serialize> AssetSaver for PostcardAssetSaver<A> {
type Asset = A;
type Settings = ();
type OutputLoader = ();
type Error = PostcardAssetError;

fn save<'a>(
&'a self,
writer: &'a mut bevy::asset::io::Writer,
asset: bevy::asset::saver::SavedAsset<'a, Self::Asset>,
_settings: &'a Self::Settings,
) -> BoxedFuture<'a, Result<<Self::OutputLoader as AssetLoader>::Settings, Self::Error>> {
Box::pin(async move {
let bytes = to_stdvec(&asset.get())?;
writer.write_all(&bytes).await?;
Ok(())
})
}
}

0 comments on commit d5f7d6e

Please sign in to comment.