use bevy::{ core_pipeline::{ core_3d::graph::Core3d, tonemapping::{DebandDither, Tonemapping}, }, render::{ camera::{CameraMainTextureUsages, CameraRenderGraph, Exposure}, primitives::Frustum, view::{ColorGrading, VisibleEntities}, }, }; use crate::prelude::*; #[derive(Error, Debug)] pub(crate) enum SaveEntityParseError { #[error("Failed to parse `{0}`")] Component(String), #[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())) } } #[derive(Debug, PartialEq)] pub(crate) enum Token { Tag(String), Str(String), Num(f32), Comment(String), Etc, } /// /// Simple function to tokenize a string into an array of Token values /// pub(crate) fn tokenize(line: &str) -> Vec { let mut l = line; let mut tokens = Vec::new(); // Check for comment if let Ok((_, (_start, content))) = tuple((char::<&str, ()>(';'), not_line_ending))(line) { tokens.push(Token::Comment(content.strip_prefix(" ").unwrap().into())); } else { // Check for all other token types in a loop while l.len() > 0 { if let Ok((rem, (_, _, s, _, _))) = tuple(( space0, char::<&str, ()>('"'), take_until("\""), char::<&str, ()>('"'), space0, ))(l) { debug!("Parsed string {:?}", s); tokens.push(Token::Str(s.into())); l = rem; } else if let Ok((rem, (_, num, _))) = tuple((space0, float::<&str, ()>, space0))(l) { debug!("Parsed float {:?}", num); tokens.push(Token::Num(num.into())); l = rem; } else if let Ok((rem, (_, etc, _))) = tuple((space0, tag::<&str, &str, ()>("..."), space0))(l) { debug!("Parsed etc. {:?}", etc); tokens.push(Token::Etc); l = rem; } else if let Ok((rem, (_, tag, _))) = tuple((space0, alphanumeric1::<&str, ()>, space0))(l) { debug!("Parsed tag {:?}", tag); tokens.push(Token::Tag(tag.into())); l = rem; } else { debug!("Breaking loop"); break; } } } debug!("Parsed tokens: {:?}", tokens); tokens } #[test] fn test_tokenize() { let line = "foo \"bar\" 1.23 baz -3.45 \"asdf\" \"multi word string\" etc"; assert_eq!( tokenize(line), vec![ Token::Tag("foo".into()), Token::Str("bar".into()), Token::Num(1.23), Token::Tag("baz".into()), Token::Num(-3.45), Token::Str("asdf".into()), Token::Str("multi word string".into()), Token::Tag("etc".into()) ] ); } /// /// Returns reflected `Transform` /// /// A fairly complicated parse function because Transform is sort of 3 components in one /// (translation, rotation, scale). /// /// It might get more complicated as I add more ways to express rotation! /// pub(crate) fn parse_save_transform( tokens: &Vec, ) -> Result>, SaveEntityParseError> { if tokens.get(0) == Some(&Token::Tag("transform".into())) { let mut t = Transform::default(); let mut idx = 1; while idx < tokens.len() { match tokens.get(idx) { Some(Token::Tag(attr)) => { idx += 1; match attr.as_str() { "translation" => { if let Token::Num(x) = tokens[idx] { t.translation.x = x; idx += 1; } if let Token::Num(y) = tokens[idx] { t.translation.y = y; idx += 1; } if let Token::Num(z) = tokens[idx] { t.translation.z = z; idx += 1; } info!("{:?}", t.translation); } "scale" => { if let Token::Num(x) = tokens[idx] { t.scale.x = x; idx += 1; } if let Token::Num(y) = tokens[idx] { t.scale.y = y; idx += 1; } if let Token::Num(z) = tokens[idx] { t.scale.z = z; idx += 1; } } "rotation" => { let x = match tokens.get(idx) { Some(Token::Num(x)) => { idx += 1; *x } _ => 0.0, }; let y = match tokens.get(idx) { Some(Token::Num(y)) => { idx += 1; *y } _ => 0.0, }; let z = match tokens.get(idx) { Some(Token::Num(z)) => { idx += 1; *z } _ => 0.0, }; let w = match tokens.get(idx) { Some(Token::Num(w)) => { idx += 1; *w } _ => 1.0, }; t.rotation = Quat::from_xyzw(x, y, z, w); } _ => idx += 1, } } _ => idx += 1, } } println!("Parsed transform: {:?}", t); Ok(vec![ t.clone_value(), GlobalTransform::default().clone_value(), ]) } else { return Err(SaveEntityParseError::Component("Transform".into())); } } #[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 tokens = tokenize(line); let parsed = parse_save_transform(&tokens).unwrap(); let t = 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), }; let gt = GlobalTransform::default(); let expected = vec![t.clone_value(), gt.clone_value()]; parsed.iter().zip(expected).for_each(|(p, e)| { assert!(e.clone_value().reflect_partial_eq(p.as_reflect()).unwrap()); }); } { let line = "transform translation ... rotation ... scale ..."; let tokens = tokenize(line); let parsed = parse_save_transform(&tokens).unwrap(); let t = Transform::default(); let gt = GlobalTransform::default(); let expected = vec![t.clone_value(), gt.clone_value()]; parsed.iter().zip(expected).for_each(|(p, e)| { assert!(e.clone_value().reflect_partial_eq(p.as_reflect()).unwrap()); }); } { let line = "transform ..."; let tokens = tokenize(line); let parsed = parse_save_transform(&tokens).unwrap(); let t = Transform::default(); let gt = GlobalTransform::default(); let expected = vec![t.clone_value(), gt.clone_value()]; parsed.iter().zip(expected).for_each(|(p, e)| { assert!(e.clone_value().reflect_partial_eq(p.as_reflect()).unwrap()); }); } { let line = "transform translation 1.0 ... rotation 2.0 ... scale 3.0 ..."; let tokens = tokenize(line); let parsed = parse_save_transform(&tokens).unwrap(); let t = Transform { translation: Vec3::new(1.0, 0.0, 0.0), rotation: Quat::from_xyzw(2.0, 0.0, 0.0, 1.0), scale: Vec3::new(3.0, 1.0, 1.0), }; let gt = GlobalTransform::default(); let expected = vec![t.clone_value(), gt.clone_value()]; parsed.iter().zip(expected).for_each(|(p, e)| { assert!(e.clone_value().reflect_partial_eq(p.as_reflect()).unwrap()); }); } } /// /// Returns a reflected `Name` /// pub(crate) fn parse_save_name( tokens: &Vec, ) -> Result>, SaveEntityParseError> { if let Some((Token::Tag(t), &[Token::Str(ref s)])) = tokens.split_first() && *t == String::from("name") { Ok(vec![Name::new(s.clone()).clone_value()]) } else { Err(SaveEntityParseError::Component("Name".into())) } } #[test] fn test_parse_name() { { let line = "name Van"; let tokens = tokenize(line); let parsed = parse_save_name(&tokens).unwrap(); let expected = vec![Name::new("Van").clone_value()]; parsed.iter().zip(expected).for_each(|(p, e)| { assert!(e.clone_value().reflect_partial_eq(p.as_reflect()).unwrap()); }); } { let line = "name Big Mike"; let tokens = tokenize(line); let parsed = parse_save_name(&tokens).unwrap(); let expected = vec![Name::new("Big Mike").clone_value()]; parsed.iter().zip(expected).for_each(|(p, e)| { assert!(e.clone_value().reflect_partial_eq(p.as_reflect()).unwrap()); }); } } #[derive(Debug, Default, Reflect, PartialEq)] #[reflect(Component, PartialEq)] pub(crate) struct SaveModel { gltf_file: PathBuf, scene_name: String, } impl Component for SaveModel { const STORAGE_TYPE: StorageType = StorageType::Table; fn register_component_hooks(hooks: &mut ComponentHooks) { todo!("Assign Scene Handle for SaveModel") } } /// /// Returns a reflected `SaveModel` /// pub(crate) fn parse_save_model( tokens: &Vec, ) -> Result>, SaveEntityParseError> { if tokens.get(0) == Some(&Token::Tag("model".into())) { if let Token::Str(gltf_file) = tokens.get(1).expect("model requires gltf file") { if let Token::Str(scene_name) = tokens.get(2).expect("model requires scene name") { Ok(vec![SaveModel { gltf_file: gltf_file.into(), scene_name: scene_name.clone(), } .clone_value()]) } else { Err(SaveEntityParseError::Component("Model".into())) } } else { Err(SaveEntityParseError::Component("Model".into())) } } else { Err(SaveEntityParseError::Component("Model".into())) } } #[test] fn test_parse_model() { let line = "model \"models/foo.glb\" \"My Scene\""; let tokens = tokenize(line); let parsed = parse_save_model(&tokens).unwrap(); let expected = vec![SaveModel { gltf_file: "models/foo.glb".into(), scene_name: "My Scene".into(), }]; parsed.iter().zip(expected).for_each(|(p, e)| { assert!(e.clone_value().reflect_partial_eq(p.as_reflect()).unwrap()); }); } #[derive(Component, Debug, Default, PartialEq, Reflect, Clone)] #[reflect_value(Component, Default, PartialEq)] pub(crate) enum SaveCameraRenderTarget { #[default] Default, Window(PathBuf), } /// /// Returns a reflected `Camera3d` /// pub(crate) fn parse_save_camera( tokens: &Vec, ) -> Result>, SaveEntityParseError> { if tokens[0] == Token::Tag("camera".into()) { Ok(vec![ Camera::default().clone_value(), CameraRenderGraph::new(Core3d).clone_value(), Projection::default().clone_value(), VisibleEntities::default().clone_value(), Frustum::default().clone_value(), Camera3d::default().clone_value(), Tonemapping::default().clone_value(), DebandDither::default().clone_value(), ColorGrading::default().clone_value(), Exposure::default().clone_value(), CameraMainTextureUsages::default().clone_value(), ]) } else { Err(SaveEntityParseError::Component("camera".into())) } } #[test] fn test_parse_camera() { { let line = "camera"; let tokens = tokenize(line); let parsed = parse_save_camera(&tokens).unwrap(); let expected = vec![ Camera::default().clone_value(), CameraRenderGraph::new(Core3d).clone_value(), Projection::default().clone_value(), VisibleEntities::default().clone_value(), Frustum::default().clone_value(), Camera3d::default().clone_value(), Tonemapping::default().clone_value(), DebandDither::default().clone_value(), ColorGrading::default().clone_value(), Exposure::default().clone_value(), CameraMainTextureUsages::default().clone_value(), ]; parsed .iter() .zip(expected) .for_each(|(p, e)| match e.reflect_partial_eq(p.as_reflect()) { Some(r) => assert!(r), None => warn!( "Type {:?} does not support reflection", e.get_represented_type_info().unwrap() ), }); } // { // let line = "camera target window"; // let tokens = tokenize(line); // let parsed = parse_save_camera(&tokens).unwrap(); // let expected = SaveCameraRenderTarget::Default; // assert!(expected // .clone_value() // .reflect_partial_eq(parsed.as_reflect()) // .unwrap()); // } // { // let line = "camera target"; // let tokens = tokenize(line); // let parsed = parse_save_camera(&tokens); // assert!(parsed.is_err()); // } // { // let line = "camera target window \"some.entity\""; // let tokens = tokenize(line); // let parsed = parse_save_camera(&tokens).unwrap(); // let expected = SaveCameraRenderTarget::Window("some.entity".into()); // assert!(expected // .clone_value() // .reflect_partial_eq(parsed.as_reflect()) // .unwrap()); // } { let line = "notcamera"; let tokens = tokenize(line); let parsed = parse_save_camera(&tokens); assert!(parsed.is_err()); } } /// encapsulates the spatial 3d bits of an entity pub(crate) fn parse_save_visibility( tokens: &Vec, ) -> Result>, SaveEntityParseError> { if tokens[0] == Token::Tag("visible".into()) { Ok(vec![ Visibility::default().clone_value(), InheritedVisibility::default().clone_value(), ViewVisibility::default().clone_value(), ]) } else { Err(SaveEntityParseError::Component("visibility".into())) } } #[test] fn test_parse_visibility() { let line = "visible"; let tokens = tokenize(line); let parsed = parse_save_visibility(&tokens).unwrap(); let expected = vec![ Visibility::default().clone_value(), InheritedVisibility::default().clone_value(), ViewVisibility::default().clone_value(), ]; parsed.iter().zip(expected).for_each(|(p, e)| { assert!(e.clone_value().reflect_partial_eq(p.as_reflect()).unwrap()); }); } /// SaveParent entity which is a reference to which entity this is a child of /// A run-time system converts this to a bevy Parent component #[derive(PartialEq, Debug, Reflect, Clone)] #[reflect_value(Component, PartialEq)] pub(crate) struct SaveParent(String); impl Component for SaveParent { const STORAGE_TYPE: StorageType = StorageType::Table; fn register_component_hooks(hooks: &mut ComponentHooks) { todo!("Assign parent for entity") } } /// /// Parses a parent entity with this format: /// ```text /// parent some_other_file.entity /// ``` /// /// Returns a reflected `SaveParent` /// pub(crate) fn parse_save_parent( tokens: &Vec, ) -> Result, SaveEntityParseError> { todo!("parse_save_parent"); // Ok(SaveParent(parent_file.into()).clone_value()) } #[test] fn test_parse_parent() { let line = "parent \"some_other_file.entity\""; let tokens = tokenize(line); let parsed = parse_save_parent(&tokens).unwrap(); let expected = SaveParent("some_other_file.entity".into()); assert!(expected .clone_value() .reflect_partial_eq(parsed.as_reflect()) .unwrap()); } /// /// Parse the Window component (which is very big!) /// We only sparsely define the bits that we want to edit (for now) /// /// Returns a reflected `Window` /// pub(crate) fn parse_save_window( tokens: &Vec, ) -> Result, SaveEntityParseError> { todo!("parse_save_window"); /* Ok(Window { title: window_title.into(), visible: visibility, ..default() } .clone_value()) */ } #[test] fn test_parse_window() { let line = "window \"Editor\" visible false"; let tokens = tokenize(line); let parsed = parse_save_window(&tokens).unwrap(); let expected = Window { visible: false, title: "Editor".into(), ..default() }; assert!(expected .clone_value() .reflect_partial_eq(parsed.as_reflect()) .unwrap()); } /// /// The UI Text bundle specified as a sparse subset of a bundle /// ```text /// uiText "This is the text" color #abc123 size 12.34 /// ``` /// /// Returns a reflected `Text` /// pub(crate) fn parse_save_ui_text( tokens: &Vec, ) -> Result, SaveEntityParseError> { todo!("parse_save_ui_text"); /* let style = TextStyle { color: Color::Srgba(Srgba::hex(hex_color).unwrap()), font_size, ..default() }; Ok(Text::from_section(text, style).clone_value()) */ } #[test] fn test_save_ui_text() { let line = "uiText \"This is the text\" color #caffee size 14.6"; let tokens = tokenize(line); let parsed = parse_save_ui_text(&tokens).unwrap(); let expected = Text::from_section( "This is the text", TextStyle { color: Srgba::hex("#caffee").unwrap().into(), font_size: 14.6, ..default() }, ); assert!(expected .clone_value() .reflect_partial_eq(parsed.as_reflect()) .unwrap()); } /// /// Returns a parser function for a basic stand-alone tag /// e.g., "editor_tag" or "ui_node" /// pub(crate) fn parse_save_tag( t: &str, ) -> impl FnOnce(&Vec) -> Result, SaveEntityParseError> + '_ { move |tokens: &Vec| -> Result, SaveEntityParseError> { todo!("parse_save_tag") } } #[derive(Component, Reflect, PartialEq, Debug, Default)] #[reflect(Component)] struct TestingTag; #[test] fn test_save_tag() { let line = "testing"; let tokens = tokenize(line); let parsed = parse_save_tag::("testing")(&tokens).unwrap(); let expected = TestingTag; assert!(expected .clone_value() .reflect_partial_eq(parsed.as_reflect()) .unwrap()); } #[derive(Clone, Debug, Reflect, PartialEq)] #[reflect(Component)] pub(crate) struct SaveTargetCamera(String); impl Component for SaveTargetCamera { const STORAGE_TYPE: StorageType = StorageType::Table; fn register_component_hooks(hooks: &mut ComponentHooks) { todo!("Assign target camera") } } /// Parses the a SaveTargetCamera which at runtime is converted to a TargetCamera /// ```text /// targetCamera "./level-camera.entity" /// ``` /// /// Returns reflected `SaveTargetCamera` pub(crate) fn parse_save_target_camera( tokens: &Vec, ) -> Result, SaveEntityParseError> { todo!("parse_save_target_camera") } #[test] fn test_target_camera() { let line = "targetCamera \"./level-camera.entity\""; let tokens = tokenize(line); let parsed = parse_save_target_camera(&tokens).unwrap(); let expected = SaveTargetCamera("./level-camera.entity".into()); assert!(expected .clone_value() .reflect_partial_eq(parsed.as_reflect()) .unwrap()); }