mod engine; mod gun; mod ship; mod system; mod util; pub use engine::Engine; pub use gun::{Gun, Projectile}; pub use ship::{EnginePoint, GunPoint, Ship}; pub use system::{Object, System}; use anyhow::{bail, Context, Result}; use std::collections::HashMap; use std::{fs::File, io::Read, path::Path}; use toml; use walkdir::WalkDir; mod syntax { use super::HashMap; use super::{engine, gun, ship, system}; use serde::Deserialize; #[derive(Debug, Deserialize)] pub struct Root { pub gun: Option>, pub ship: Option>, pub system: Option>, pub engine: Option>, } } trait Build { /// Build a processed System struct from raw serde data fn build(root: &syntax::Root) -> Result> where Self: Sized; } /// Represents generic game content, not connected to any game objects. #[derive(Debug)] pub struct Content { pub systems: Vec, pub ships: Vec, pub guns: Vec, pub engines: Vec, } macro_rules! quick_name_dup_check { ($array:expr, $root:ident, $build:expr) => {{ let mut p = $build(&$root)?; for s in &$array { for o in &p { if s.name == o.name { bail!( "Error parsing content: duplicate ship names `{}` and `{}`", s.name, o.name ) } } } $array.append(&mut p); }}; } impl Content { fn try_parse(path: &Path) -> Result { let mut file_string = String::new(); let _ = File::open(path)?.read_to_string(&mut file_string); let file_string = file_string.trim(); return Ok(toml::from_str(&file_string)?); } fn add_root(&mut self, root: syntax::Root) -> Result<()> { quick_name_dup_check!(self.systems, root, system::System::build); quick_name_dup_check!(self.guns, root, gun::Gun::build); quick_name_dup_check!(self.ships, root, ship::Ship::build); quick_name_dup_check!(self.engines, root, engine::Engine::build); return Ok(()); } pub fn load_dir(path: &str) -> Result { let mut content = Self { systems: Vec::new(), ships: Vec::new(), guns: Vec::new(), engines: Vec::new(), }; for e in WalkDir::new(path).into_iter().filter_map(|e| e.ok()) { if e.metadata().unwrap().is_file() { // TODO: better warnings match e.path().extension() { Some(t) => { if t.to_str() != Some("toml") { println!("[WARNING] {e:#?} is not a toml file, skipping."); continue; } } None => { println!("[WARNING] {e:#?} is not a toml file, skipping."); continue; } } let path = e.path(); let root = Self::try_parse(path) .with_context(|| format!("Could not load {:#?}", e.path()))?; content .add_root(root) .with_context(|| format!("Could not parse {}", path.display()))?; } } return Ok(content); } }