From c444890f8857fd2386c7d81a76fbdcd0a0115448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20B=C3=BChne?= <64769435+Cactus-man@users.noreply.github.com> Date: Thu, 18 Jul 2024 14:23:13 +0200 Subject: [PATCH] Add support for json lines --- CHANGELOG.md | 3 ++ Cargo.toml | 12 ++++-- README.md | 4 +- assets/trees.level.jsonl | 6 +++ examples/jsonl.rs | 74 ++++++++++++++++++++++++++++++++ src/jsonl.rs | 93 ++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 5 +++ 7 files changed, 193 insertions(+), 4 deletions(-) create mode 100644 assets/trees.level.jsonl create mode 100644 examples/jsonl.rs create mode 100644 src/jsonl.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index fdec003..f034193 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## v0.12.0 - 18.07.2024 +- Support for [Json Lines](https://jsonlines.org/) + ## v0.11.0 - 04.07.2024 - Update to Bevy 0.14 - Update `quick-xml` to `0.34` diff --git a/Cargo.toml b/Cargo.toml index 9cb9fc9..cfa29f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_common_assets" -version = "0.11.0" +version = "0.12.0" authors = ["Niklas Eicker "] edition = "2021" license = "MIT OR Apache-2.0" @@ -17,6 +17,7 @@ ron = ["dep:serde_ron"] toml = ["dep:serde_toml"] yaml = ["dep:serde_yaml"] json = ["dep:serde_json"] +jsonl = ["dep:serde_json"] msgpack = ["dep:rmp-serde"] xml = ["dep:quick-xml"] csv = ["dep:csv"] @@ -31,10 +32,10 @@ serde_json = { version = "1", optional = true } rmp-serde = { version = "1", optional = true } csv = { version = "1", optional = true } thiserror = "1.0" -quick-xml = { version = "0.34", features = [ "serialize" ], optional = true } +quick-xml = { version = "0.34", features = ["serialize"], optional = true } serde = { version = "1" } anyhow = { version = "1" } -postcard = {version = "1.0", features = ["use-std"], optional = true} +postcard = { version = "1.0", features = ["use-std"], optional = true } [dev-dependencies] bevy = { version = "0.14.0-rc.4" } @@ -74,6 +75,11 @@ name = "json" path = "examples/json.rs" required-features = ["json"] +[[example]] +name = "jsonl" +path = "examples/jsonl.rs" +required-features = ["jsonl"] + [[example]] name = "xml" path = "examples/xml.rs" diff --git a/README.md b/README.md index bbe9a79..53f3d03 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Supported formats: | `xml` | `xml` | [`xml.rs`](./examples/xml.rs) | | `yaml` | `yaml` | [`yaml.rs`](./examples/yaml.rs) | | `csv` | `csv` | [`csv.rs`](./examples/csv.rs) | +| `jsonl` | `jsonl` | [`jsonl.rs`](./examples/jsonl.rs) | ## Usage @@ -49,6 +50,7 @@ fn main() { .add_plugins(( DefaultPlugins, JsonAssetPlugin::::new(&["level.json", "custom.json"]), + JsonLinesAssetPlugin::::new(&["level.jsonl", "custom.jsonl"]), RonAssetPlugin::::new(&["level.ron"]), MsgPackAssetPlugin::::new(&["level.msgpack"]), PostcardAssetPlugin::::new(&["level.postcard"]), @@ -79,7 +81,7 @@ Compatibility of `bevy_common_assets` versions: | `bevy_common_assets` | `bevy` | |:---------------------|:-------| -| `0.11` | `0.14` | +| `0.11` - `0.12` | `0.14` | | `0.10` | `0.13` | | `0.8` - `0.9` | `0.12` | | `0.7` | `0.11` | diff --git a/assets/trees.level.jsonl b/assets/trees.level.jsonl new file mode 100644 index 0000000..f5da25b --- /dev/null +++ b/assets/trees.level.jsonl @@ -0,0 +1,6 @@ +[42.0, 42.0, 0.0] +[4.0, 32.0, 0.0] +[54.0, 7.0, 0.0] +[-61.0, 4.0, 0.0] +[-6.0, -72.0, 0.0] +[6.0, -89.0, 0.0] diff --git a/examples/jsonl.rs b/examples/jsonl.rs new file mode 100644 index 0000000..86b5e13 --- /dev/null +++ b/examples/jsonl.rs @@ -0,0 +1,74 @@ +use bevy::prelude::*; +use bevy::reflect::TypePath; +use bevy_common_assets::jsonl::JsonLinesAssetPlugin; + +fn main() { + App::new() + .add_plugins(( + DefaultPlugins, + JsonLinesAssetPlugin::::new(&["level.jsonl"]), + )) + .insert_resource(Msaa::Off) + .init_state::() + .add_systems(Startup, setup) + .add_systems(Update, spawn_level.run_if(in_state(AppState::Loading))) + .run(); +} + +fn setup(mut commands: Commands, asset_server: Res) { + let level = LevelHandle(asset_server.load("trees.level.jsonl")); + 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, + tree: Res, + mut levels: ResMut>, + mut state: ResMut>, +) { + if let Some(level) = levels.remove(level.0.id()) { + for position in level.positions { + commands.spawn(SpriteBundle { + transform: Transform::from_translation(position.0.into()), + texture: tree.0.clone(), + ..default() + }); + } + + state.set(AppState::Level); + } +} + +#[derive(serde::Deserialize)] +#[serde(transparent)] +struct TreePosition([f32; 3]); + +#[derive(serde::Deserialize, Asset, TypePath)] +struct Level { + positions: Vec, +} + +impl FromIterator for Level { + fn from_iter>(iter: T) -> Self { + let positions = Vec::from_iter(iter); + Self { positions } + } +} + +#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, States)] +enum AppState { + #[default] + Loading, + Level, +} + +#[derive(Resource)] +struct ImageHandle(Handle); + +#[derive(Resource)] +struct LevelHandle(Handle); diff --git a/src/jsonl.rs b/src/jsonl.rs new file mode 100644 index 0000000..0b86961 --- /dev/null +++ b/src/jsonl.rs @@ -0,0 +1,93 @@ +use bevy::app::{App, Plugin}; +use bevy::asset::io::Reader; +use bevy::asset::{Asset, AssetApp, AssetLoader, AsyncReadExt, LoadContext}; +use serde_json::from_str; +use std::io::BufRead; +use std::marker::PhantomData; +use thiserror::Error; + +/// Plugin to load your asset type `A` via a list of ` from jsonl files. +pub struct JsonLinesAssetPlugin { + extensions: Vec<&'static str>, + _marker: PhantomData<(A, D)>, +} + +impl Plugin for JsonLinesAssetPlugin +where + for<'de> D: serde::Deserialize<'de> + Sync + Send + 'de, + for<'de> A: FromIterator + Asset + Sync + Send + 'de, +{ + fn build(&self, app: &mut App) { + app.init_asset::() + .register_asset_loader(JsonLinesAssetLoader:: { + extensions: self.extensions.clone(), + _marker: PhantomData, + }); + } +} + +impl JsonLinesAssetPlugin +where + for<'de> D: serde::Deserialize<'de> + Sync + Send + 'de, + for<'de> A: FromIterator + Asset + Sync + Send + 'de, +{ + /// 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, + } + } +} + +/// Loads your asset type `A` from jsonl files +pub struct JsonLinesAssetLoader { + extensions: Vec<&'static str>, + _marker: PhantomData<(A, D)>, +} + +/// Possible errors that can be produced by [`JsonLinesAssetLoader`] +#[non_exhaustive] +#[derive(Debug, Error)] +pub enum JsonLinesLoaderError { + /// An [IO Error](std::io::Error) + #[error("Could not read the file: {0}")] + Io(#[from] std::io::Error), + /// A [JSON Error](serde_json::error::Error) + #[error("Could not parse the JSON: {0}")] + JsonError(#[from] serde_json::error::Error), +} + +impl AssetLoader for JsonLinesAssetLoader +where + for<'de> D: serde::Deserialize<'de> + Sync + Send + 'de, + for<'de> A: FromIterator + Asset + Sync + Send + 'de, +{ + type Asset = A; + type Settings = (); + type Error = JsonLinesLoaderError; + + async fn load<'a>( + &'a self, + reader: &'a mut Reader<'_>, + _settings: &'a (), + _load_context: &'a mut LoadContext<'_>, + ) -> Result { + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes).await?; + + if let Some(b'\n') = bytes.last() { + // Json Lines may optionally end with a line break. + let _ = bytes.pop(); + } + + bytes + .lines() + .map(|line| Ok::(from_str(line?.as_str())?)) + .collect() + } + + fn extensions(&self) -> &[&str] { + &self.extensions + } +} diff --git a/src/lib.rs b/src/lib.rs index 5fe2ce4..ceda34e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,6 +56,10 @@ pub mod csv; #[cfg_attr(docsrs, doc(cfg(feature = "json")))] #[cfg(feature = "json")] pub mod json; +/// Module containing a Bevy plugin to load assets from `jsonl` files with custom file extensions. +#[cfg_attr(docsrs, doc(cfg(feature = "jsonl")))] +#[cfg(feature = "jsonl")] +pub mod jsonl; /// Module containing a Bevy plugin to load assets from `MessagePack` files with custom file extensions. #[cfg_attr(docsrs, doc(cfg(feature = "msgpack")))] #[cfg(feature = "msgpack")] @@ -83,6 +87,7 @@ pub mod yaml; #[cfg(all( feature = "json", + feature = "jsonl", feature = "msgpack", feature = "ron", feature = "toml",