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 .register_type::() .register_type::() .register_type::() .init_asset::() .init_asset_loader::() .init_asset_loader::() .add_systems(Startup, test_load_entity) .add_systems( Update, spawn_loaded_entities.run_if(on_event::>()), ); } } #[derive(Asset, TypePath, Default)] pub(crate) struct SaveEntity { components: Vec>, } // TODO: SCALABILITY: SaveEntity should maybe be a HashSet or Vec of Box // Then we tell the parse "For each line, run through each of these concrete parsers" // If it matches, add it to the set of Box // Ironically we basically want DynamicEntity: https://docs.rs/bevy/latest/bevy/scene/struct.DynamicEntity.html 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.components.push(name.clone_value()); } else if let Ok(transform) = parse::parse_save_transform(line) { entity.components.push(transform.clone_value()); } else if let Ok(uuid) = parse::parse_save_uuid(line) { entity.components.push(uuid.clone_value()); } else if let Ok((gltf_file, scene_name)) = parse::parse_save_model(line) { let gltf_scene = GltfScene { gltf: load_context.load(gltf_file), name: scene_name }; entity.components.push(gltf_scene.clone_value()); } else if let Ok(target) = parse::parse_save_camera(line) { entity.components.push(target.clone_value()); } else { error!("Failed to parse line component `{:?}`", line); } }); Ok(entity) } } #[derive(Component, Clone, Debug, Reflect, PartialEq)] #[reflect(Component)] struct GltfScene { gltf: Handle, name: String, } 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>>), #[error("Failed to parse camera")] Camera, } // 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)) } /// /// 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 { 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); } /// /// 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); } } #[derive(Component, Clone, Debug, Reflect, PartialEq)] #[reflect(Component)] pub(crate) struct EntityUuid { id: String } /// /// pub(crate) fn parse_save_uuid(line: &str) -> Result { let (remainder, _) = tag("uuid")(line)?; let id = remainder.trim().into(); Ok(EntityUuid { id }) } #[test] fn test_parse_uuid() { let line = "uuid 1c16ab9a-5f79-4340-8469-4086f69c64f2"; let parsed = parse_save_uuid(line).unwrap(); let expected = EntityUuid { id: "1c16ab9a-5f79-4340-8469-4086f69c64f2".into() }; assert_eq!(parsed, expected); } /// /// pub(crate) fn parse_save_model(line: &str) -> Result<(String, String), SaveEntityParseError> { let (rem, (_, _, gltf_path, _, scene_name)) = tuple(( tag("model"), space1, parse_string, space1, parse_string, ))(line)?; debug_assert!(rem == ""); Ok((gltf_path.into(), scene_name.into())) } #[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); } #[derive(Component, Debug, Default, PartialEq, Reflect, Clone)] #[reflect_value(Component, Default)] pub(crate) enum SaveCameraRenderTarget { #[default] Default, Window(Uuid), } pub(crate) fn parse_save_camera(line: &str) -> Result { if let Ok((rem, _camera)) = tag::<&str, &str, ()>("camera")(line) { if let Ok((rem, (_, _target))) = tuple((space1::<&str, ()>, tag("target")))(rem) { if let Ok((rem, (_, _window))) = tuple((space1::<&str, ()>, tag("window")))(rem) { // Camera + target + window + UUID if let Ok((rem, (_, uuid))) = tuple((space1::<&str, ()>, take(36usize)))(rem) { debug_assert!(rem == ""); let parsed_uuid = Uuid::parse_str(uuid)?; Ok(SaveCameraRenderTarget::Window(parsed_uuid)) // Camera + target + widow } else { debug_assert!(rem == ""); Ok(SaveCameraRenderTarget::Default) } // camera + target } else { debug_assert!(rem == ""); Err(SaveEntityParseError::Camera) } // Just camera } else { debug_assert!(rem == ""); Ok(SaveCameraRenderTarget::Default) } // Nothing parsed well } else { Err(SaveEntityParseError::Camera) } } #[test] fn test_parse_camera() { { let line = "camera"; let parsed = parse_save_camera(line).unwrap(); let expected = SaveCameraRenderTarget::Default; assert_eq!(parsed, expected); } { let line = "camera target window"; let parsed = parse_save_camera(line).unwrap(); let expected = SaveCameraRenderTarget::Default; assert_eq!(parsed, expected); } { let line = "camera target"; let parsed = parse_save_camera(line); assert!(parsed.is_err()); } { let line = "camera target window 9a1367e0-71e5-4c79-b4ad-02aa44b68af0"; let target_uuid = Uuid::parse_str("9a1367e0-71e5-4c79-b4ad-02aa44b68af0").unwrap(); let parsed = parse_save_camera(line).unwrap(); let expected = SaveCameraRenderTarget::Window(target_uuid); 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"] } } #[derive(Default)] struct SaveSceneLoader; impl AssetLoader for SaveSceneLoader { type Asset = DynamicScene; 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 asset_path = load_context.path().to_path_buf(); let parent_dir = asset_path.parent().unwrap(); let _sub_assets: Vec> = s.lines().map(|line| { parent_dir.join(line) }).map(|path| { load_context.load(path) }).collect(); Ok(DynamicScene::default()) } fn extensions(&self) -> &[&str] { &["scene"] } } fn test_load_entity(loader: Res, mut commands: Commands) { let _: Handle = loader.load("editor/_.scene"); let handle: Handle = loader.load("levels/00/entities/van.entity"); commands.spawn(( SpatialBundle { ..default() }, handle, )); } fn spawn_loaded_entities( query: Query<(Entity, &Handle)>, mut events: EventReader>, save_entities: Res>, mut commands: Commands, ) { 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(); // Get entity with SaveEntity handle let mut e = commands.entity(entity); // Clear the entity e.despawn_descendants(); // Populate with reflected components saved.components.iter().for_each(|dc| { e.insert_reflect(dc.clone_value()); }); }); } }) }