diff --git a/.cargo/config.toml b/.cargo/config.toml index fb4b0bd..5f5fde7 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -25,6 +25,10 @@ rustflags = [ [target.x86_64-pc-windows-msvc] linker = "rust-lld.exe" # Use LLD Linker +rustflags = [ + "-Zshare-generics=n", # This needs to be off if you use dynamic linking on Windows. + "-Zthreads=0" +] [target.wasm32-unknown-unknown] runner = "wasm-server-runner" diff --git a/Cargo.toml b/Cargo.toml index b2976df..e8d196b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,17 +15,20 @@ uuid = "1.7.0" clap = { version = "4", features = ["derive"] } chrono = { version = "0.4" } -# Enable a small amount of optimization in debug mode [profile.dev] opt-level = 1 -# Enable high optimizations for dependencies (incl. Bevy), but not for our code: [profile.dev.package."*"] opt-level = 3 -# Prioritize binary size for wasm +[profile.release] +codegen-units = 1 +lto = "thin" + [profile.wasm-release] inherits = "release" -opt-level = "z" +# opt-level = "z" +opt-level = "s" lto = "fat" codegen-units = 1 +strip = "debuginfo" diff --git a/assets/editor/_.scene b/assets/editor/_.scene new file mode 100644 index 0000000..f1c7e2b --- /dev/null +++ b/assets/editor/_.scene @@ -0,0 +1,4 @@ +entities/camera.entity +entities/ui_container.entity +entities/ui_title.entity +entities/window.entity \ No newline at end of file diff --git a/assets/editor/entities/camera.entity b/assets/editor/entities/camera.entity new file mode 100644 index 0000000..431e0d0 --- /dev/null +++ b/assets/editor/entities/camera.entity @@ -0,0 +1,6 @@ +uuid 739619d7-b8a1-42b4-8803-2b8cfedeebf1 +name "Editor Camera" +editor_tag +camera render_target window b90f31a2-34d9-42c2-9e53-646d9c9c6c70 +fly_camera +transform transform 10.0 10.0 10.0 \ No newline at end of file diff --git a/assets/editor/entities/ui_container.entity b/assets/editor/entities/ui_container.entity new file mode 100644 index 0000000..d773f03 --- /dev/null +++ b/assets/editor/entities/ui_container.entity @@ -0,0 +1,4 @@ +uuid 08e1a4d9-6c76-471d-af02-1aa7db1b435a +editor_tag +ui_node +target_camera 739619d7-b8a1-42b4-8803-2b8cfedeebf1 \ No newline at end of file diff --git a/assets/editor/entities/ui_title.entity b/assets/editor/entities/ui_title.entity new file mode 100644 index 0000000..c5e5b78 --- /dev/null +++ b/assets/editor/entities/ui_title.entity @@ -0,0 +1,4 @@ +uuid 0088bc7d-851e-402b-9d20-fadfe5cab106 +editor_tag +ui_text text "Welcome to the editor" color 1.0 1.0 1.0 1.0 +parent 08e1a4d9-6c76-471d-af02-1aa7db1b435a \ No newline at end of file diff --git a/assets/editor/entities/window.entity b/assets/editor/entities/window.entity new file mode 100644 index 0000000..f04551a --- /dev/null +++ b/assets/editor/entities/window.entity @@ -0,0 +1,4 @@ +uuid b90f31a2-34d9-42c2-9e53-646d9c9c6c70 +editor_tag +name "Editor Window" +window title "Editor" visible false \ No newline at end of file diff --git a/assets/levels/00/_.scene b/assets/levels/00/_.scene new file mode 100644 index 0000000..0afcefb --- /dev/null +++ b/assets/levels/00/_.scene @@ -0,0 +1,2 @@ +entities/camera.entity +entities/van.entity \ No newline at end of file diff --git a/src/editor.rs b/src/editor.rs index 76c3606..d31f877 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -1,5 +1,3 @@ -use bevy::render::primitives::Aabb; - use crate::prelude::*; pub(crate) struct EditorPlugin; @@ -7,18 +5,12 @@ pub(crate) struct EditorPlugin; impl Plugin for EditorPlugin { fn build(&self, app: &mut App) { app.init_state::() - .add_systems(Startup, init_editor) + .add_systems(OnEnter(EditorState::Open), spawn_editor) + .add_systems(OnExit(EditorState::Open), despawn_editor) .add_systems( Update, toggle_editor_state.run_if(input_just_pressed(KeyCode::F3)), ) - .add_systems( - Update, - ( - toggle_editor_window, - toggle_entity_aabb, - ).run_if(state_changed::), - ) .add_systems( Update, ( @@ -64,57 +56,6 @@ impl std::convert::From<&EditorState> for bool { #[derive(Component)] struct EditorTag; -/// At startup initialize some editor components -fn init_editor(mut commands: Commands) { - // Editor window - let editor_window = commands - .spawn(( - EditorTag, - Window { - visible: false, - title: "Editor".into(), - ..default() - }, - )) - .id(); - - // Editor camera - let editor_camera = commands - .spawn(( - EditorTag, - FlyCamera, - Camera3dBundle { - camera: Camera { - target: RenderTarget::Window(WindowRef::Entity(editor_window)), - ..default() - }, - transform: Transform::from_xyz(10.0, 10.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y), - ..default() - }, - )) - .id(); - - // Editor UI elements - commands - .spawn(( - EditorTag, - TargetCamera(editor_camera), - NodeBundle { ..default() }, - )) - .with_children(|parent| { - parent.spawn(( - EditorTag, - TextBundle::from_section( - "Welcome to the editor", - TextStyle { - color: WHITE.into(), - ..default() - }, - ), - )); - }); -} - fn toggle_editor_state( curr_state: Res>, mut next_state: ResMut>, @@ -132,18 +73,16 @@ fn toggle_editor_state( } } -fn toggle_editor_window( - state: Res>, - mut windows: Query<&mut Window, With>, - mut cameras: Query<&mut Camera, With>, +fn spawn_editor( + mut _commands: Commands, ) { - let curr = state.get().into(); - cameras.iter_mut().for_each(|mut camera| { - camera.is_active = curr; - }); - windows.iter_mut().for_each(|mut window| { - window.visible = curr; - }) + todo!("Spawn editor"); +} + +fn despawn_editor( + mut _comands: Commands, +) { + todo!("Despawn editor"); } /// Handles window close requests which are only weird because of the editor @@ -184,44 +123,4 @@ fn plane_gizmos(mut gizmos: Gizmos) { gizmos.arrow(Vec3::ZERO, Vec3::Y, DARK_GREEN); gizmos.arrow(Vec3::ZERO, Vec3::Z, BLUE); } -} - -fn toggle_entity_aabb( - state: Res>, - query: Query>>, - children: Query<&Children>, - aabbs: Query<(Entity, &Aabb)>, - mut commands: Commands, -) { - let add_component = match state.get() { - EditorState::Open => true, - EditorState::Closed => false, - }; - query.iter().for_each(|root| { - if add_component { - commands.entity(root).insert(ShowAabbGizmo { ..default() }); - } else { - commands.entity(root).remove::(); - } - - let mut child_has_bounding_box = false; - for child in children.iter_descendants(root) { - if add_component { - commands.entity(child).insert(ShowAabbGizmo { ..default() }); - } else { - commands.entity(child).remove::(); - } - if aabbs.contains(child) { - commands.entity(child).insert(Aabb { center: Vec3A::ZERO, half_extents: Vec3A::ONE }); - } else { - child_has_bounding_box = true; - } - } - - if !aabbs.contains(root) && !child_has_bounding_box { - commands.entity(root).insert(Aabb { center: Vec3A::ZERO, half_extents: Vec3A::ONE }); - } else { - commands.entity(root).remove::(); - } - }) } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 1f5d308..9f0df45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,9 @@ pub(crate) mod save; /// Window handling pub(crate) mod window; +/// Save file parsing +pub(crate) mod parser; + use crate::prelude::*; fn main() { diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..dccf05d --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,327 @@ +use crate::prelude::*; + +#[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()); + } +} + + +/// 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(Component, PartialEq, Debug, Reflect, Clone)] +#[reflect_value(Component)] +pub(crate) struct SaveParent(String); + +/// Parses a parent entity with this format: +/// ```text +/// parent some_other_file.entity +/// ``` +pub(crate) fn parse_save_parent(line: &str) -> Result { + todo!() +} + +#[test] +fn test_parse_parent() { + let line = "parent some_other_file.entity"; + let parsed = parse_save_parent(line).unwrap(); + let expected = SaveParent("some_other_file.entity".into()); + assert_eq!(parsed, expected); +} + +/// +/// Parse the Window component (which is very big!) +/// We only sparsely define the bits that we want to edit (for now) +/// +pub(crate) fn parse_save_window(line: &str) -> Result { + todo!() +} + +#[test] +fn test_parse_window() { + let line = "window \"Editor\" visible false"; + let parsed = parse_save_window(line).unwrap(); + let expected = Window { visible: false, title: "Editor".into(), ..default() }; + assert_eq!(parsed.visible, expected.visible); + assert_eq!(parsed.title, expected.title); +} + +/// +/// The UI Text bundle specified as a sparse subset of a bundle +/// +pub(crate) fn parse_save_ui_text(line: &str) -> Result { + todo!() +} + +#[test] +fn test_save_ui_text() { + todo!() +} + +/// +/// Returns a parser function for a basic stand-alone tag +/// e.g., "editor_tag" or "ui_node" +pub(crate) fn parse_save_tag(tag: &str) -> impl FnOnce(&str) -> Result { + move |line: &str| -> Result { + todo!() + } +} + +#[test] +fn test_save_tag() { + todo!() +} + +#[derive(Component, Clone, Debug, Reflect, PartialEq)] +#[reflect(Component)] +pub(crate) struct SaveTargetCamera(String); + +/// Parses the a SaveTargetCamera which at runtime is converted to a TargetCamera +pub(crate) fn parse_save_target_camera(line: &str) -> Result { + todo!() +} + +#[test] +fn test_target_camera() { + todo!() +} \ No newline at end of file diff --git a/src/prelude.rs b/src/prelude.rs index c0203ac..86c693e 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,14 +1,15 @@ pub(crate) use bevy::{ app::AppExit, asset::{io::Reader, AssetLoader, LoadContext, AsyncReadExt}, - color::palettes::css::{WHITE, GRAY, RED, DARK_GREEN, BLUE}, + color::palettes::css::{GRAY, RED, DARK_GREEN, BLUE}, gltf::Gltf, input::common_conditions::input_just_pressed, - math::Vec3A, prelude::*, render::camera::RenderTarget, window::{WindowRef, WindowCloseRequested, PrimaryWindow}, }; +pub(crate) use bevy::ecs::reflect::ReflectCommandExt; +pub(crate) use nom::bytes::complete::take; pub(crate) use nom::{ IResult, bytes::complete::{tag, take_until1}, @@ -19,8 +20,4 @@ pub(crate) use nom::{ pub(crate) use uuid::Uuid; pub(crate) use thiserror::Error; -pub(crate) use crate::{ - camera::*, - conditions::*, - save::*, -}; +pub(crate) use crate::{conditions::*, parser::*}; diff --git a/src/save.rs b/src/save.rs index 338fe06..b765af5 100644 --- a/src/save.rs +++ b/src/save.rs @@ -5,258 +5,72 @@ pub(crate) struct SavePlugin; impl Plugin for SavePlugin { fn build(&self, app: &mut App) { - app.init_asset::() + app + .register_type::() + .register_type::() + .register_type::() + .init_asset::() .init_asset_loader::() - .add_systems(Startup, test_save_entity) + .init_asset_loader::() + .add_systems(Startup, test_load_entity) .add_systems( Update, - check_loaded_entity_assets.run_if(on_event::>()), + spawn_loaded_entities.run_if(on_event::>()), ); } } -#[derive(Asset, TypePath, Debug, Default)] +#[derive(Asset, TypePath, 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, + 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 { + ) -> 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); + + if let Ok(name) = parse_save_name(line) { + entity.components.push(name.clone_value()); + } else if let Ok(transform) = parse_save_transform(line) { + entity.components.push(transform.clone_value()); + } else if let Ok(uuid) = parse_save_uuid(line) { + entity.components.push(uuid.clone_value()); + } else if let Ok((gltf_file, scene_name)) = 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_save_camera(line) { + entity.components.push(target.clone_value()); + } else if let Ok(parent) = parse_save_parent(line) { + entity.components.push(parent.clone_value()); + } else if let Ok(window) = parse_save_window(line) { + entity.components.push(window.clone_value()); + // } else if let Ok(ui_text_bundle) = parse_save_ui_text(line) { + // entity.components.push(ui_text_bundle.clone_value()); + } else if let Ok(target_camera) = parse_save_target_camera(line) { + entity.components.push(target_camera.clone_value()); + } else { + error!("Failed to parse line component `{:?}`", line); } }); 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(Component, Clone, Debug, Reflect, PartialEq)] +#[reflect(Component)] +struct GltfScene { + gltf: Handle, + name: String, } #[derive(Default)] @@ -267,7 +81,7 @@ enum SaveEntityLoaderError { #[error("Could not load asset: {0}")] Io(#[from] std::io::Error), #[error("Could not parse entity: {0}")] - Parse(#[from] parse::SaveEntityParseError), + Parse(#[from] SaveEntityParseError), } impl AssetLoader for SaveEntityLoader { @@ -294,7 +108,44 @@ impl AssetLoader for SaveEntityLoader { } } -fn test_save_entity(loader: Res, mut commands: Commands) { +#[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() }, @@ -302,12 +153,11 @@ fn test_save_entity(loader: Res, mut commands: Commands) { )); } -fn check_loaded_entity_assets( +fn spawn_loaded_entities( 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 { @@ -316,51 +166,15 @@ fn check_loaded_entity_assets( .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 - ); + // Get entity with SaveEntity handle 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::>(); - } + // Clear the entity + e.despawn_descendants(); + // Populate with reflected components + saved.components.iter().for_each(|dc| { + e.insert_reflect(dc.clone_value()); + }); }); } })