diff --git a/Cargo.lock b/Cargo.lock index 11e1a71..e616d04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,6 +78,9 @@ version = "0.1.0" dependencies = [ "bevy", "bevy_mod_picking", + "nom", + "thiserror", + "uuid", "wee_alloc", ] diff --git a/Cargo.toml b/Cargo.toml index 8db6bd7..fcad64a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,10 @@ edition = "2021" bevy = "0.13" wee_alloc = "*" bevy_mod_picking = "0.18" +thiserror = "1" +nom = "7" +# Entity Uuid parsing +uuid = "1.7.0" [profile.wasm-release] inherits = "release" diff --git a/assets/levels/00/entities/van.entity b/assets/levels/00/entities/van.entity new file mode 100644 index 0000000..8b156a7 --- /dev/null +++ b/assets/levels/00/entities/van.entity @@ -0,0 +1,3 @@ +name "van" +uuid 5c270e84-814c-4d51-9ccd-ab79d9e01f1d +transform translation 0.0 0.0 0.0 rotation 0.0 0.0 0.0 1.0 scale 1.0 1.0 1.0 \ No newline at end of file diff --git a/assets/models/van.blend b/assets/models/van.blend new file mode 100644 index 0000000..e005a29 Binary files /dev/null and b/assets/models/van.blend differ diff --git a/assets/models/van.glb b/assets/models/van.glb new file mode 100644 index 0000000..84f68ea Binary files /dev/null and b/assets/models/van.glb differ diff --git a/src/main.rs b/src/main.rs index 3a67267..2760d65 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,8 @@ pub(crate) mod editor; pub(crate) mod menu; /// Helper module containing common imports across the project pub(crate) mod prelude; +/// Level saving/loading logic +pub(crate) mod save; /// Window handling pub(crate) mod window; @@ -24,6 +26,7 @@ fn main() { .add_plugins(camera::CameraPlugin) .add_plugins(editor::EditorPlugin) .add_plugins(menu::MenuPlugin) + .add_plugins(save::SavePlugin) .add_plugins(window::WindowPlugin) .run(); } diff --git a/src/prelude.rs b/src/prelude.rs index c662a8a..82ff9e6 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,10 +1,18 @@ pub(crate) use bevy::app::AppExit; +pub(crate) use bevy::asset::AsyncReadExt; +pub(crate) use bevy::asset::{io::Reader, AssetLoader, LoadContext}; pub(crate) use bevy::input::common_conditions::input_just_pressed; pub(crate) use bevy::prelude::*; pub(crate) use bevy::render::camera::RenderTarget; +pub(crate) use bevy::utils::Uuid; +pub(crate) use bevy::utils::{thiserror::Error, BoxedFuture}; pub(crate) use bevy::window::PrimaryWindow; pub(crate) use bevy::window::WindowCloseRequested; pub(crate) use bevy::window::WindowRef; +pub(crate) use nom::bytes::complete::tag; +pub(crate) use nom::bytes::complete::take; +pub(crate) use nom::number::complete::float; +pub(crate) use nom::sequence::tuple; pub(crate) use crate::camera::*; pub(crate) use crate::conditions::*; diff --git a/src/save.rs b/src/save.rs new file mode 100644 index 0000000..c8f3384 --- /dev/null +++ b/src/save.rs @@ -0,0 +1,232 @@ +use nom::IResult; + +use crate::prelude::*; + +/// Menu Plugin; empty struct for Plugin impl +pub(crate) struct SavePlugin; + +impl Plugin for SavePlugin { + fn build(&self, app: &mut App) { + app.init_asset::() + .init_asset_loader::() + .add_systems(Startup, test_save_entity); + } +} + +#[derive(Asset, TypePath, Debug, Default)] +struct SaveEntity { + transform: Option, + name: Option, + uuid: Option, +} + +impl SaveEntity { + fn parse(s: &str) -> Result { + let lines = s.split('\n'); + let mut entity = SaveEntity { ..default() }; + lines.into_iter().for_each(|line| { + if let Ok(name) = parse_save_name(line) { + entity.name = Some(name); + } else if let Ok(transform) = parse_save_transform(line) { + entity.transform = Some(transform); + } else if let Ok(uuid) = parse_save_uuid(line) { + entity.uuid = Some(uuid); + } + }); + Ok(entity) + } +} + +#[derive(Error, Debug)] +enum SaveEntityParseError { + #[error("Failed to parse Uuid: {0}")] + Uuid(#[from] uuid::Error), + #[error("Failed to parse name")] + Name, + #[error("Failed to parse Transform")] + Transform, + #[error("Failed to parse Entity")] + Nom(nom::Err>>), +} + +// Convert Nom error to parse error +// https://stackoverflow.com/a/77974858/3096574 +impl From>> for SaveEntityParseError { + fn from(err: nom::Err>) -> Self { + SaveEntityParseError::Nom(err.map_input(|input| input.into())) + } +} + +fn parse_thing<'a>(s: &'a str, l: &'a str) -> IResult<&'a str, &'a str> { + tag(l)(s) +} + +fn parse_xyz<'a>(i: &'a str) -> IResult<&'a str, (f32, f32, f32)> { + tuple((float, take(1usize), float, take(1usize), float))(i) + .map(|(s, (x, _, y, _, z))| (s, (x, y, z))) +} + +fn parse_wxyz<'a>(i: &'a str) -> IResult<&'a str, (f32, f32, f32, f32)> { + tuple(( + float, + take(1usize), + float, + take(1usize), + float, + take(1usize), + float, + ))(i) + .map(|(s, (w, _, x, _, y, _, z))| (s, (w, x, y, z))) +} + +/// +/// ``` +/// let parsed = parse_save_transform("transform translation 1.0 2.0 3.0 rotation 1.0 0.0 0.0 0.0 scale 1.0 1.0 1.0").unwrap(); +/// let expected = Transform { translation: Vec3::new(1.0, 1.0, 1.0), rotation: Quat::from_xzyw(1.0, 0.0, 0.0, 0.0), scale: Vec3::ONE }; +/// +/// assert_eq!(parsed, expected); +/// ``` +/// +fn parse_save_transform(line: &str) -> Result { + let (rem, _) = tag("transform")(line)?; + + let mut transform = Transform::default(); + + let mut curr = rem.trim_start(); + for _ in 0..3 { + if let Ok((input, _)) = parse_thing(curr, "translation") { + let (rem, (x, y, z)) = parse_xyz(input.trim_start())?; + transform.translation = Vec3::new(x, y, z); + curr = rem.trim_start(); + } else if let Ok((input, _)) = parse_thing(curr.trim_start(), "rotation") { + let (rem, (x, y, z, w)) = parse_wxyz(input.trim_start())?; + transform.rotation = Quat::from_xyzw(x, y, z, w); + curr = rem.trim_start(); + } else if let Ok((input, _)) = parse_thing(curr.trim_start(), "scale") { + let (rem, (x, y, z)) = parse_xyz(input.trim_start())?; + transform.scale = Vec3::new(x, y, z); + curr = rem.trim_start(); + } else { + return Err(SaveEntityParseError::Transform); + } + } + + Ok(transform) +} + +/// +/// ``` +/// let parsed = parse_save_name("name asfd").unwrap(); +/// let expected = Name::new("asdf"); +/// +/// assert_eq!(parsed, expected); +/// ``` +/// +fn parse_save_name(line: &str) -> Result { + let (remainder, _) = tag("name")(line)?; + let n = remainder.trim().to_string(); + if n.len() == 0 { + Err(SaveEntityParseError::Name) + } else { + let name = Name::new(n); + Ok(name) + } +} + +/// +/// ``` +/// let parsed = parse_save_uuid("uuid 339dd18f-761a-4997-b515-ef57f4dc2447").unwrap(); +/// let expected = Uuid::parse_str("339dd18f-761a-4997-b515-ef57f4dc2447").unwrap(); +/// +/// assert_eq!(parsed, expected); +/// ``` +/// +fn parse_save_uuid(line: &str) -> Result { + let (remainder, _) = tag("uuid")(line)?; + let uuid = Uuid::try_parse(remainder.trim())?; + Ok(uuid) +} + +mod test { + use super::*; + + #[test] + fn test_parse_transform() { + let line = "transform translation 1.0 2.0 3.0 rotation 0.1 0.2 0.3 1.0 scale 1.1 1.2 1.3"; + let parsed = parse_save_transform(line).unwrap(); + let expected = Transform { + translation: Vec3::new(1.0, 2.0, 3.0), + rotation: Quat::from_xyzw(0.1, 0.2, 0.3, 1.0), + scale: Vec3::new(1.1, 1.2, 1.3), + }; + + assert_eq!(parsed, expected); + } + + #[test] + fn test_parse_name() { + { + let line = "name Van"; + let parsed = parse_save_name(line).unwrap(); + let expected = Name::new("Van"); + + assert_eq!(parsed, expected); + } + { + let line = "name Big Mike"; + let parsed = parse_save_name(line).unwrap(); + let expected = Name::new("Big Mike"); + + assert_eq!(parsed, expected); + } + } + + #[test] + fn test_parse_uuid() { + let line = "uuid 1c16ab9a-5f79-4340-8469-4086f69c64f2"; + let parsed = parse_save_uuid(line).unwrap(); + let expected = Uuid::parse_str("1c16ab9a-5f79-4340-8469-4086f69c64f2").unwrap(); + + assert_eq!(parsed, expected); + } +} + +#[derive(Default)] +struct SaveEntityLoader; + +#[derive(Error, Debug)] +enum SaveEntityLoaderError { + #[error("Could not load asset: {0}")] + Io(#[from] std::io::Error), + #[error("Could not parse entity: {0}")] + Parse(#[from] SaveEntityParseError), +} + +impl AssetLoader for SaveEntityLoader { + type Asset = SaveEntity; + type Settings = (); + type Error = SaveEntityLoaderError; + + fn load<'a>( + &'a self, + reader: &'a mut Reader, + _settings: &'a (), + _load_context: &'a mut LoadContext, + ) -> BoxedFuture<'a, Result> { + Box::pin(async move { + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes).await?; + + let s = std::str::from_utf8(bytes.as_slice()).unwrap(); + Ok(SaveEntity::parse(s)?) + }) + } + + fn extensions(&self) -> &[&str] { + &["entity"] + } +} + +fn test_save_entity(loader: Res, mut handle: Local>) { + *handle = loader.load("levels/00/entities/van.entity"); +}