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) .add_systems( Update, check_loaded_entity_assets.run_if(on_event::>()), ); } } #[derive(Asset, TypePath, Debug, Default)] pub(crate) struct SaveEntity { transform: Option, name: Option, uuid: Option, model: Option<(Handle, String)>, // TODO: Option feels like an antipattern as this is either Some(true) or None and never Some(false). // Either encode more Camera data in this or replace it with Option<()> or just bool. camera: Option, } impl SaveEntity { fn parse( text: &str, load_context: &mut LoadContext, ) -> Result { let lines = text.split('\n'); let mut entity = SaveEntity { ..default() }; lines.into_iter().for_each(|line| { if let Ok(name) = parse::parse_save_name(line) { entity.name = Some(name); } else if let Ok(transform) = parse::parse_save_transform(line) { entity.transform = Some(transform); } else if let Ok(uuid) = parse::parse_save_uuid(line) { entity.uuid = Some(EntityUuid(uuid)); } else if let Ok((gltf_path, scene_name)) = parse::parse_save_model(line) { let handle = load_context.load(gltf_path); entity.model = Some((handle, scene_name)); } else if let Ok(true) = parse::parse_save_camera(line) { entity.camera = Some(true); } }); Ok(entity) } } #[derive(Component, Clone, Debug)] struct EntityUuid(Uuid); mod parse { use super::*; #[derive(Error, Debug)] pub(crate) 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_word<'a>(w: &'a str) -> impl Fn(&'a str) -> IResult<&'a str, &'a str> { move |i: &'a str| { tag(w)(i) } } fn parse_xyz(i: &str) -> IResult<&str, (f32, f32, f32)> { tuple((float, space1, float, space1, float))(i) .map(|(s, (x, _, y, _, z))| (s, (x, y, z))) } fn parse_wxyz(i: &str) -> IResult<&str, (f32, f32, f32, f32)> { tuple(( float, space1, float, space1, float, space1, float, ))(i) .map(|(s, (w, _, x, _, y, _, z))| (s, (w, x, y, z))) } fn parse_string(i: &str) -> IResult<&str, &str> { let (rem, (_, out, _)) = tuple((tag("\""), take_until1("\""), tag("\"")))(i)?; Ok((rem, out)) } /// /// ``` /// 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); /// ``` /// pub(crate) 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 { println!("Curr: {:?}", curr); if let Ok((rem, (_, _, (x, y, z)))) = tuple((parse_word("translation"), space1, parse_xyz))(curr.trim_start()) { transform.translation = Vec3::new(x, y, z); curr = rem.trim_start(); } else if let Ok((rem, (_, _, (x, y, z, w)))) = tuple((parse_word("rotation"), space1, parse_wxyz))(curr.trim_start()) { transform.rotation = Quat::from_xyzw(x, y, z, w); curr = rem.trim_start(); } else if let Ok((rem, (_, _, (x, y, z)))) = tuple((parse_word("scale"), space1, parse_xyz))(curr.trim_start()) { transform.scale = Vec3::new(x, y, z); curr = rem.trim_start(); } else { return Err(SaveEntityParseError::Transform); } } // Assert there are no trailing characters on the line debug_assert_eq!(curr, ""); Ok(transform) } #[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); } /// /// ``` /// let parsed = parse_save_name("name asfd").unwrap(); /// let expected = Name::new("asdf"); /// /// assert_eq!(parsed, expected); /// ``` /// pub(crate) fn parse_save_name(line: &str) -> Result { let (remainder, _) = tag("name")(line)?; let n = remainder.trim().to_string(); if n.is_empty() { Err(SaveEntityParseError::Name) } else { let name = Name::new(n); Ok(name) } } #[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); } } /// /// ``` /// 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); /// ``` /// pub(crate) fn parse_save_uuid(line: &str) -> Result { let (remainder, _) = tag("uuid")(line)?; let uuid = Uuid::try_parse(remainder.trim())?; Ok(uuid) } #[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); } pub(crate) fn parse_save_model(line: &str) -> Result<(String, String), SaveEntityParseError> { let (rem, (_, _, gltf_name, _, scene_name)) = tuple(( tag("model"), space1, parse_string, space1, parse_string, ))(line)?; debug_assert_eq!(rem, ""); Ok((String::from(gltf_name), String::from(scene_name))) } #[test] fn test_parse_model() { let line = "model \"models/foo.glb\" \"My Scene\""; let parsed = parse_save_model(line).unwrap(); let expected = (String::from("models/foo.glb"), String::from("My Scene")); assert_eq!(parsed, expected); } pub(crate) fn parse_save_camera(line: &str) -> Result { let (_rem, cam) = tag("camera")(line)?; Ok(!cam.is_empty()) } #[test] fn test_parse_camera() { { let line = "camera"; let parsed = parse_save_camera(line).unwrap(); let expected = true; assert_eq!(parsed, expected); } { let line = "notcamera"; let parsed = parse_save_camera(line); assert!(parsed.is_err()); } } } #[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] parse::SaveEntityParseError), } impl AssetLoader for SaveEntityLoader { type Asset = SaveEntity; type Settings = (); type Error = SaveEntityLoaderError; 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?; let s = std::str::from_utf8(bytes.as_slice()).unwrap(); let save_entity = SaveEntity::parse(s, load_context)?; Ok(save_entity) } fn extensions(&self) -> &[&str] { &["entity"] } } fn test_save_entity(loader: Res, mut commands: Commands) { let handle: Handle = loader.load("levels/00/entities/van.entity"); commands.spawn(( SpatialBundle { ..default() }, handle, )); } fn check_loaded_entity_assets( query: Query<(Entity, &Handle)>, mut events: EventReader>, save_entities: Res>, mut commands: Commands, gltfs: Res>, ) { events.read().for_each(|event| { if let AssetEvent::LoadedWithDependencies { id } = event { query .iter() .filter(|(_, handle)| handle.id() == *id) .for_each(|(entity, _handle)| { let saved = save_entities.get(*id).unwrap(); debug!( "Updating entity {:?} ({:?}) because asset changed", saved.name, saved.uuid ); let mut e = commands.entity(entity); // Apply camera // Should be applied early to avoid clobbering transform if let Some(is_camera) = &saved.camera { if *is_camera { e.insert(Camera3dBundle { ..default() }); } } else { e.remove::<(Camera, Camera3d)>(); } // Apply transform if let Some(transform) = &saved.transform { // TODO: Only update if different e.insert(*transform); } else { e.remove::(); } // Apply Name if let Some(name) = &saved.name { // TODO: Only update if different e.insert(name.clone()); } else { e.remove::(); } // Apply Uuid if let Some(uuid) = &saved.uuid { // TODO: Only update if different e.insert(uuid.clone()); } else { e.remove::(); } // Apply Model if let Some((gltf_handle, scene_name)) = &saved.model { // Find scene and update let gltf = gltfs.get(gltf_handle).unwrap(); let scene_handle = gltf.named_scenes.get(scene_name.as_str().into()).unwrap(); e.insert(scene_handle.clone()); } else { e.remove::>(); } }); } }) }