From d8f67c2cef7c47968899c9287e87e18ff758e38e Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Mon, 19 Aug 2024 21:47:48 -0700 Subject: [PATCH] Better framework for asset-driven entities --- assets/editor/_.scene | 4 - assets/editor/entities/camera.entity | 5 -- assets/editor/entities/ui_container.entity | 3 - assets/editor/entities/ui_title.entity | 3 - assets/editor/entities/window.entity | 3 - assets/levels/00/_.scene | 2 - assets/levels/00/entities/camera.entity | 3 - assets/levels/00/entities/van.entity | 3 - assets/scenes/00.scene | 1 + assets/scenes/00/a.entity | 1 + src/main.rs | 11 +-- src/parser.rs | 66 ++++++++------- src/prelude.rs | 7 +- src/save.rs | 96 ++++++++++++++-------- 14 files changed, 110 insertions(+), 98 deletions(-) delete mode 100644 assets/editor/_.scene delete mode 100644 assets/editor/entities/camera.entity delete mode 100644 assets/editor/entities/ui_container.entity delete mode 100644 assets/editor/entities/ui_title.entity delete mode 100644 assets/editor/entities/window.entity delete mode 100644 assets/levels/00/_.scene delete mode 100644 assets/levels/00/entities/camera.entity delete mode 100644 assets/levels/00/entities/van.entity create mode 100644 assets/scenes/00.scene create mode 100644 assets/scenes/00/a.entity diff --git a/assets/editor/_.scene b/assets/editor/_.scene deleted file mode 100644 index f1c7e2b..0000000 --- a/assets/editor/_.scene +++ /dev/null @@ -1,4 +0,0 @@ -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 deleted file mode 100644 index 62fdcbb..0000000 --- a/assets/editor/entities/camera.entity +++ /dev/null @@ -1,5 +0,0 @@ -name "Editor Camera" -editorTag -camera target window "window.entity" -flyCamera -transform translation 10.0 10.0 10.0 rotation 0.0 0.0 0.0 1.0 scale 1.0 1.0 1.0 diff --git a/assets/editor/entities/ui_container.entity b/assets/editor/entities/ui_container.entity deleted file mode 100644 index a048f52..0000000 --- a/assets/editor/entities/ui_container.entity +++ /dev/null @@ -1,3 +0,0 @@ -editorTag -uiNode -targetCamera "camera.entity" diff --git a/assets/editor/entities/ui_title.entity b/assets/editor/entities/ui_title.entity deleted file mode 100644 index 3cbe8bf..0000000 --- a/assets/editor/entities/ui_title.entity +++ /dev/null @@ -1,3 +0,0 @@ -editorTag -uiText "Welcome to the editor" color #ffffff size 12.0 -parent "ui_container.entity" diff --git a/assets/editor/entities/window.entity b/assets/editor/entities/window.entity deleted file mode 100644 index 830a94f..0000000 --- a/assets/editor/entities/window.entity +++ /dev/null @@ -1,3 +0,0 @@ -editorTag -name "Editor Window" -window "Editor" visible false diff --git a/assets/levels/00/_.scene b/assets/levels/00/_.scene deleted file mode 100644 index 0afcefb..0000000 --- a/assets/levels/00/_.scene +++ /dev/null @@ -1,2 +0,0 @@ -entities/camera.entity -entities/van.entity \ No newline at end of file diff --git a/assets/levels/00/entities/camera.entity b/assets/levels/00/entities/camera.entity deleted file mode 100644 index 057a2af..0000000 --- a/assets/levels/00/entities/camera.entity +++ /dev/null @@ -1,3 +0,0 @@ -name camera -transform translation 2.0 2.0 0.0 rotation 0.0 0.0 0.0 1.0 scale 1.0 1.0 1.0 -camera diff --git a/assets/levels/00/entities/van.entity b/assets/levels/00/entities/van.entity deleted file mode 100644 index 8ef3600..0000000 --- a/assets/levels/00/entities/van.entity +++ /dev/null @@ -1,3 +0,0 @@ -name van -transform translation 0.0 0.0 0.0 rotation 0.0 0.0 0.0 1.0 scale 1.0 1.0 1.0 -model "models/van.glb" "Scene" diff --git a/assets/scenes/00.scene b/assets/scenes/00.scene new file mode 100644 index 0000000..37e8e3d --- /dev/null +++ b/assets/scenes/00.scene @@ -0,0 +1 @@ +00/a.entity diff --git a/assets/scenes/00/a.entity b/assets/scenes/00/a.entity new file mode 100644 index 0000000..20cc19d --- /dev/null +++ b/assets/scenes/00/a.entity @@ -0,0 +1 @@ +name "Hello world" diff --git a/src/main.rs b/src/main.rs index 32b2b34..ffd36ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +#![feature(let_chains)] + /// Camera controller pub(crate) mod camera; /// ECS scheduling `run_if` conditions @@ -12,7 +14,6 @@ pub(crate) mod prelude; pub(crate) mod save; /// Window handling pub(crate) mod window; - /// Save file parsing pub(crate) mod parser; @@ -26,10 +27,10 @@ fn main() { exit_condition: bevy::window::ExitCondition::OnPrimaryClosed, ..default() })) - .add_plugins(camera::CameraPlugin) - .add_plugins(editor::EditorPlugin) - .add_plugins(menu::MenuPlugin) + // .add_plugins(camera::CameraPlugin) + // .add_plugins(editor::EditorPlugin) + // .add_plugins(menu::MenuPlugin) + // .add_plugins(window::WindowPlugin) .add_plugins(save::SavePlugin) - .add_plugins(window::WindowPlugin) .run(); } diff --git a/src/parser.rs b/src/parser.rs index 0d96b17..5988356 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,14 +2,10 @@ use crate::prelude::*; #[derive(Error, Debug)] pub(crate) enum SaveEntityParseError { - #[error("Failed to parse name")] - Name, - #[error("Failed to parse Transform")] - Transform, + #[error("Failed to parse `{0}`")] + Component(String), #[error("Failed to parse Entity")] Nom(nom::Err>>), - #[error("Failed to parse camera")] - Camera, } // Convert Nom error to parse error @@ -22,7 +18,7 @@ impl From>> for SaveEntityParseError { #[derive(Debug, PartialEq)] -enum Token { +pub(crate) enum Token { Tag(String), Str(String), Num(f32), @@ -32,27 +28,35 @@ enum Token { /// Simple function to tokenize a string into an array of Token values /// pub(crate) fn tokenize(line: &str) -> Vec { - line.split(" ") - .map(|piece| { - if let Ok(n) = piece.parse::() { - Token::Num(n) - } else if let Ok((_, (_, s, _))) = tuple(( - tag::<&str, &str, ()>("\""), - take_until1("\""), - tag::<&str, &str, ()>("\""), - ))(piece) - { - Token::Str(s.into()) - } else { - Token::Tag(piece.into()) - } - }) - .collect() + let mut l = line; + let mut tokens = Vec::new(); + + while l.len() > 0 { + debug!("Line: {:?}", l); + if let Ok((rem, (_, s, _))) = tuple((char::<&str, ()>('"'), take_until("\""), char::<&str, ()>('"')))(l) { + debug!("Parsed string {:?}", s); + tokens.push(Token::Str(s.into())); + l = rem; + } else if let Ok((rem, num)) = float::<&str, ()>(l) { + debug!("Parsed float {:?}", num); + tokens.push(Token::Num(num.into())); + 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 + } + } + + tokens } #[test] fn test_tokenize() { - let line = "foo \"bar\" 1.23 baz \"asdf\" etc"; + let line = "foo \"bar\" 1.23 baz -3.45 \"asdf\" \"multi word string\" etc"; assert_eq!( tokenize(line), vec![ @@ -60,7 +64,9 @@ fn test_tokenize() { 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()) ] ); @@ -71,8 +77,6 @@ fn test_tokenize() { /// Returns reflected `Transform` /// pub(crate) fn parse_save_transform(tokens: &Vec) -> Result, SaveEntityParseError> { - let mut transform = Transform::default(); - // Tag(Transform), // Tag(Translation), Number, Number, Number // Tag(Rotation), Number, Number, Number, Number @@ -104,9 +108,11 @@ fn test_parse_transform() { /// Returns a reflected `Name` /// pub(crate) fn parse_save_name(tokens: &Vec) -> Result, SaveEntityParseError> { - todo!("parse_save_name"); - - // Err(SaveEntityParseError::Name) + if let Some((Token::Tag(t), &[Token::Str(ref s)])) = tokens.split_first() && *t == String::from("name") { + Ok(Name::new(s.clone()).clone_value()) + } else { + Err(SaveEntityParseError::Component("Name".into())) + } } #[test] @@ -374,7 +380,7 @@ fn test_save_ui_text() { /// pub(crate) fn parse_save_tag( t: &str, -) -> impl FnOnce(&str) -> Result, SaveEntityParseError> + '_ { +) -> impl FnOnce(&Vec) -> Result, SaveEntityParseError> + '_ { move |tokens: &Vec| -> Result, SaveEntityParseError> { todo!("parse_save_tag") } diff --git a/src/prelude.rs b/src/prelude.rs index b6c6746..cf2aeb0 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -9,14 +9,11 @@ pub(crate) use bevy::{ render::camera::RenderTarget, window::{PrimaryWindow, WindowCloseRequested, WindowRef}, }; -pub(crate) use nom::character::complete::hex_digit1; pub(crate) use nom::{ - branch::alt, - bytes::complete::{tag, take_until1}, - character::complete::{char, space1}, + bytes::complete::take_until, + character::complete::{char, space0, alphanumeric1}, number::complete::float, sequence::tuple, - IResult, }; pub(crate) use std::path::PathBuf; pub(crate) use thiserror::Error; diff --git a/src/save.rs b/src/save.rs index e39e979..1eb9b34 100644 --- a/src/save.rs +++ b/src/save.rs @@ -10,24 +10,27 @@ impl Plugin for SavePlugin { .register_type::() .init_asset::() .init_asset_loader::() + .init_asset::() .init_asset_loader::() - .add_systems(Startup, test_load_entity) - .add_systems( - Update, - spawn_loaded_entities.run_if(on_event::>()), - ); + .add_systems(Startup, load_scenes) + .add_systems(Update, spawn_scenes.run_if(on_event::>())) + .add_systems(Update, spawn_entities.run_if(any_component_added::>)); } } #[derive(Asset, TypePath, Default)] -pub(crate) struct SaveEntity { - components: Vec>, +pub(crate) struct SaveScene { + entities: 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 +#[derive(Asset, TypePath, Default)] +pub(crate) struct SaveEntity { + components: Vec>, +} impl SaveEntity { fn parse( @@ -38,13 +41,13 @@ impl SaveEntity { let mut entity = SaveEntity { ..default() }; let fns = [ parse_save_name, - parse_save_transform, - parse_save_model, - parse_save_camera, - parse_save_parent, - parse_save_window, - parse_save_target_camera, - parse_save_ui_text, + // parse_save_transform, + // parse_save_model, + // parse_save_camera, + // parse_save_parent, + // parse_save_window, + // parse_save_target_camera, + // parse_save_ui_text, // parse_save_tag::("editor_tag"), ]; lines @@ -55,7 +58,7 @@ impl SaveEntity { let mut good = false; // Tokenize the line - let tokens = parser::tokenize(line); + let tokens = tokenize(line); // Run line against all parsers for f in fns { @@ -123,7 +126,7 @@ impl AssetLoader for SaveEntityLoader { struct SaveSceneLoader; impl AssetLoader for SaveSceneLoader { - type Asset = DynamicScene; + type Asset = SaveScene; type Settings = (); type Error = SaveEntityLoaderError; @@ -140,13 +143,14 @@ impl AssetLoader for SaveSceneLoader { let asset_path = load_context.path().to_path_buf(); let parent_dir = asset_path.parent().unwrap(); - let _sub_assets: Vec> = s + let entities: Vec> = s .lines() .map(|line| parent_dir.join(line)) .map(|path| load_context.load(path)) .collect(); + info!("Entities: {:?}", entities); - Ok(DynamicScene::default()) + Ok(SaveScene { entities }) } fn extensions(&self) -> &[&str] { @@ -154,16 +158,18 @@ impl AssetLoader for SaveSceneLoader { } } -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)); +/// Testing system for loading a specific scene +fn load_scenes(loader: Res, mut commands: Commands) { + let handle: Handle = loader.load("scenes/00.scene"); + info!("Loading scene {:?}", handle); + commands.spawn(handle); } -fn spawn_loaded_entities( - query: Query<(Entity, &Handle)>, - mut events: EventReader>, - save_entities: Res>, +/// Spawns scenes with the Handle marker +fn spawn_scenes( + query: Query<(Entity, &Handle)>, + mut events: EventReader>, + save_scenes: Res>, mut commands: Commands, ) { events.read().for_each(|event| { @@ -171,18 +177,44 @@ fn spawn_loaded_entities( query .iter() .filter(|(_, handle)| handle.id() == *id) - .for_each(|(entity, _handle)| { - let saved = save_entities.get(*id).unwrap(); + .for_each(|(entity, handle)| { + debug!("Spawning scene {:?} on {:?}", handle, entity); + + let scene = save_scenes.get(handle).unwrap(); // Get entity with SaveEntity handle let mut e = commands.entity(entity); - // Clear the entity + // Clear the entity of descendants e.despawn_descendants(); - // Populate with reflected components - saved.components.iter().for_each(|dc| { - e.insert_reflect(dc.clone_value()); + // Populate with entities + e.with_children(|parent| { + scene.entities.iter().for_each(|this| { + parent.spawn(this.clone()); + }); }); }); } }) } + +/// Spawns entities with the Handle component +fn spawn_entities( + events: Query<(Entity, &Handle), Added>>, + save_entities: Res>, + mut commands: Commands, +) { + events.iter().for_each(|(entity, handle)| { + debug!("Spawning entity {:?} {:?}", entity, handle); + + // Get a handle on the + let mut e = commands.entity(entity); + + // Get the entity asset containing reflected component + let save_entity = save_entities.get(handle).unwrap(); + + // Add each component to the entity + save_entity.components.iter().for_each(|component| { + e.insert_reflect(component.clone_value()); + }); + }); +}