Reworked content parser
parent
b69538b8c6
commit
aff7b3801f
|
@ -1,7 +1,6 @@
|
|||
# content type: ship
|
||||
[ship]
|
||||
name = "Gypsum"
|
||||
[ship."Gypsum"]
|
||||
sprite = "ship::gypsum"
|
||||
size = 100
|
||||
|
||||
engines = [{ x = 0.0, y = -105, size = 50.0 }]
|
||||
guns = [{ x = 0.0, y = 100 }]
|
||||
|
|
|
@ -1,27 +1,20 @@
|
|||
# content type: system
|
||||
[system]
|
||||
name = "12 Autumn above"
|
||||
[system."12 Autumn Above"]
|
||||
|
||||
[object.star]
|
||||
sprite = "star::star"
|
||||
position = [0.0, 0.0, 30.0]
|
||||
size = 2000
|
||||
object.star.sprite = "star::star"
|
||||
object.star.position = [0.0, 0.0, 30.0]
|
||||
object.star.size = 2000
|
||||
|
||||
object.earth.sprite = "planet::earth"
|
||||
object.earth.position.center = "star"
|
||||
object.earth.position.radius = 4000
|
||||
object.earth.position.angle = 0
|
||||
object.earth.position.z = 10.0
|
||||
object.earth.size = 1000
|
||||
|
||||
[object.earth]
|
||||
sprite = "planet::earth"
|
||||
position.center = "star"
|
||||
position.radius = 4000
|
||||
position.angle = 0
|
||||
position.z = 10.0
|
||||
size = 1000
|
||||
|
||||
|
||||
[object.luna]
|
||||
sprite = "planet::luna"
|
||||
position.center = "earth"
|
||||
position.radius = 1600
|
||||
position.angle = 135
|
||||
position.z = 7.8
|
||||
size = 500
|
||||
angle = -45
|
||||
object.luna.sprite = "planet::luna"
|
||||
object.luna.position.center = "earth"
|
||||
object.luna.position.radius = 1600
|
||||
object.luna.position.angle = 135
|
||||
object.luna.position.z = 7.8
|
||||
object.luna.size = 500
|
||||
object.luna.angle = -45
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
use anyhow::{Context, Result};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::{syntax, ContentType};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Content {
|
||||
pub systems: Vec<syntax::system::System>,
|
||||
pub ships: Vec<syntax::ship::Ship>,
|
||||
}
|
||||
|
||||
impl Content {
|
||||
pub fn new(cv: Vec<(PathBuf, ContentType)>) -> Result<Self> {
|
||||
let mut systems = Vec::new();
|
||||
let mut ships = Vec::new();
|
||||
|
||||
// These methods check intra-file consistency
|
||||
for (p, c) in cv {
|
||||
match c {
|
||||
ContentType::System(v) => systems.push(
|
||||
syntax::system::System::parse(v)
|
||||
.with_context(|| format!("Could not parse {}", p.display()))?,
|
||||
),
|
||||
|
||||
ContentType::Ship(v) => ships.push(
|
||||
syntax::ship::Ship::parse(v)
|
||||
.with_context(|| format!("Could not parse {}", p.display()))?,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(Self { systems, ships });
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
use anyhow::{bail, Result};
|
||||
use std::{fs::File, io::Read, path::Path};
|
||||
|
||||
use super::syntax;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ContentType {
|
||||
System(syntax::system::toml::SystemRoot),
|
||||
Ship(syntax::ship::toml::ShipRoot),
|
||||
}
|
||||
|
||||
// TODO: check content without loading game
|
||||
impl ContentType {
|
||||
pub fn try_parse(file_string: &str) -> Result<Option<Self>> {
|
||||
// TODO: More advanced parsing, read the whole top comment
|
||||
let first = match file_string.split_once("\n") {
|
||||
None => bail!("This file is empty."),
|
||||
Some((first, _)) => first,
|
||||
};
|
||||
let type_spec = first[1..].trim(); // Remove hash
|
||||
|
||||
let type_spec = if type_spec.starts_with("content type: ") {
|
||||
type_spec[14..].to_owned()
|
||||
} else {
|
||||
bail!("No content type specified")
|
||||
};
|
||||
|
||||
return Ok(match &type_spec[..] {
|
||||
"system" => Some(Self::System(toml::from_str(&file_string)?)),
|
||||
"ship" => Some(Self::Ship(toml::from_str(&file_string)?)),
|
||||
_ => bail!("Invalid content type `{}`", type_spec),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn from_path(path: &Path) -> Result<Option<Self>> {
|
||||
let mut file_string = String::new();
|
||||
let _ = File::open(path)?.read_to_string(&mut file_string);
|
||||
let file_string = file_string.trim();
|
||||
return Self::try_parse(&file_string);
|
||||
}
|
||||
}
|
|
@ -1,17 +1,86 @@
|
|||
mod content;
|
||||
mod contenttype;
|
||||
mod syntax;
|
||||
#![allow(dead_code)]
|
||||
mod ship;
|
||||
mod system;
|
||||
|
||||
pub use content::Content;
|
||||
pub use contenttype::ContentType;
|
||||
pub use syntax::ship;
|
||||
pub use syntax::system;
|
||||
pub use ship::{Engine, Gun, Ship};
|
||||
pub use system::{Object, System};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use std::collections::HashMap;
|
||||
use std::{fs::File, io::Read, path::Path};
|
||||
use toml;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
pub fn load_content_dir(path: &str) -> Result<Content> {
|
||||
let mut raw_content = Vec::new();
|
||||
mod syntax {
|
||||
use super::{ship, system, HashMap};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Root {
|
||||
pub ship: HashMap<String, ship::syntax::Ship>,
|
||||
pub system: HashMap<String, system::syntax::System>,
|
||||
}
|
||||
}
|
||||
|
||||
trait Build {
|
||||
/// Build a processed System struct from raw serde data
|
||||
fn build(root: &syntax::Root) -> Result<Vec<Self>>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Content {
|
||||
pub systems: Vec<system::System>,
|
||||
pub ships: Vec<ship::Ship>,
|
||||
}
|
||||
|
||||
impl Content {
|
||||
fn try_parse(path: &Path) -> Result<syntax::Root> {
|
||||
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<()> {
|
||||
let mut p = ship::Ship::build(&root)?;
|
||||
for s in &self.ships {
|
||||
for o in &p {
|
||||
if s.name == o.name {
|
||||
bail!(
|
||||
"Error parsing content: duplicate ship names {} and {}",
|
||||
s.name,
|
||||
o.name
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.ships.append(&mut p);
|
||||
|
||||
let mut p = system::System::build(&root)?;
|
||||
for s in &self.systems {
|
||||
for o in &p {
|
||||
if s.name == o.name {
|
||||
bail!(
|
||||
"Error parsing content: duplicate system names {} and {}",
|
||||
s.name,
|
||||
o.name
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.systems.append(&mut p);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
pub fn load_dir(path: &str) -> Result<Self> {
|
||||
let mut content = Self {
|
||||
systems: Vec::new(),
|
||||
ships: Vec::new(),
|
||||
};
|
||||
|
||||
for e in WalkDir::new(path).into_iter().filter_map(|e| e.ok()) {
|
||||
if e.metadata().unwrap().is_file() {
|
||||
// TODO: better warnings
|
||||
|
@ -28,15 +97,15 @@ pub fn load_content_dir(path: &str) -> Result<Content> {
|
|||
}
|
||||
}
|
||||
|
||||
let c = crate::content::ContentType::from_path(e.path())
|
||||
let path = e.path();
|
||||
let root = Self::try_parse(path)
|
||||
.with_context(|| format!("Could not load {:#?}", e.path()))?;
|
||||
|
||||
match c {
|
||||
Some(c) => raw_content.push((e.path().to_path_buf(), c)),
|
||||
None => continue,
|
||||
}
|
||||
content
|
||||
.add_root(root)
|
||||
.with_context(|| format!("Could not parse {}", path.display()))?;
|
||||
}
|
||||
}
|
||||
|
||||
return crate::content::Content::new(raw_content);
|
||||
return Ok(content);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
use anyhow::Result;
|
||||
use cgmath::Point2;
|
||||
|
||||
pub(super) mod syntax {
|
||||
use serde::Deserialize;
|
||||
// Raw serde syntax structs.
|
||||
// These are never seen by code outside this crate.
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Ship {
|
||||
pub sprite: String,
|
||||
pub size: f32,
|
||||
pub engines: Vec<Engine>,
|
||||
pub guns: Vec<Gun>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Engine {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub size: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Gun {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
}
|
||||
}
|
||||
|
||||
// Processed data structs.
|
||||
// These are exported.
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Ship {
|
||||
pub name: String,
|
||||
pub sprite: String,
|
||||
pub size: f32,
|
||||
pub engines: Vec<Engine>,
|
||||
pub guns: Vec<Gun>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Engine {
|
||||
pub pos: Point2<f32>,
|
||||
pub size: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Gun {
|
||||
pub pos: Point2<f32>,
|
||||
pub cooldown: f32,
|
||||
pub active_cooldown: f32,
|
||||
}
|
||||
|
||||
impl super::Build for Ship {
|
||||
fn build(root: &super::syntax::Root) -> Result<Vec<Self>> {
|
||||
let mut out = Vec::new();
|
||||
for (ship_name, ship) in &root.ship {
|
||||
out.push(Self {
|
||||
name: ship_name.to_owned(),
|
||||
sprite: ship.sprite.to_owned(),
|
||||
size: ship.size,
|
||||
engines: ship
|
||||
.engines
|
||||
.iter()
|
||||
.map(|e| Engine {
|
||||
pos: Point2 { x: e.x, y: e.y },
|
||||
size: e.size,
|
||||
})
|
||||
.collect(),
|
||||
guns: ship
|
||||
.guns
|
||||
.iter()
|
||||
.map(|e| Gun {
|
||||
pos: Point2 { x: e.x, y: e.y },
|
||||
cooldown: 0.2,
|
||||
active_cooldown: 0.0,
|
||||
})
|
||||
.collect(),
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(out);
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
#![allow(dead_code)]
|
||||
pub mod ship;
|
||||
pub mod system;
|
|
@ -1,60 +0,0 @@
|
|||
use anyhow::Result;
|
||||
use cgmath::Point2;
|
||||
|
||||
/// Toml file syntax
|
||||
pub(in crate::content) mod toml {
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ShipRoot {
|
||||
pub ship: Ship,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Ship {
|
||||
pub name: String,
|
||||
pub sprite: String,
|
||||
pub size: f32,
|
||||
pub engines: Vec<Engine>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Engine {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub size: f32,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Ship {
|
||||
pub name: String,
|
||||
pub sprite: String,
|
||||
pub size: f32,
|
||||
pub engines: Vec<Engine>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Engine {
|
||||
pub pos: Point2<f32>,
|
||||
pub size: f32,
|
||||
}
|
||||
|
||||
impl Ship {
|
||||
pub fn parse(value: toml::ShipRoot) -> Result<Self> {
|
||||
return Ok(Self {
|
||||
name: value.ship.name,
|
||||
sprite: value.ship.sprite,
|
||||
size: value.ship.size,
|
||||
engines: value
|
||||
.ship
|
||||
.engines
|
||||
.iter()
|
||||
.map(|e| Engine {
|
||||
pos: Point2 { x: e.x, y: e.y },
|
||||
size: e.size,
|
||||
})
|
||||
.collect(),
|
||||
});
|
||||
}
|
||||
}
|
|
@ -4,20 +4,15 @@ use std::collections::{HashMap, HashSet};
|
|||
|
||||
use crate::physics::Polar;
|
||||
|
||||
/// Toml file syntax
|
||||
pub(in crate::content) mod toml {
|
||||
pub(super) mod syntax {
|
||||
use super::HashMap;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SystemRoot {
|
||||
pub system: System,
|
||||
pub object: HashMap<String, Object>,
|
||||
}
|
||||
// Raw serde syntax structs.
|
||||
// These are never seen by code outside this crate.
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct System {
|
||||
pub name: String,
|
||||
pub object: HashMap<String, Object>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
@ -79,6 +74,9 @@ pub(in crate::content) mod toml {
|
|||
}
|
||||
}
|
||||
|
||||
// Processed data structs.
|
||||
// These are exported.
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct System {
|
||||
pub name: String,
|
||||
|
@ -93,15 +91,15 @@ pub struct Object {
|
|||
pub angle: Deg<f32>,
|
||||
}
|
||||
|
||||
// Helper function for resolve_position, never called on its own.
|
||||
/// Helper function for resolve_position, never called on its own.
|
||||
fn resolve_coordinates(
|
||||
objects: &HashMap<String, toml::Object>,
|
||||
cor: &toml::CoordinatesThree,
|
||||
objects: &HashMap<String, syntax::Object>,
|
||||
cor: &syntax::CoordinatesThree,
|
||||
mut cycle_detector: HashSet<String>,
|
||||
) -> Result<Point3<f32>> {
|
||||
match cor {
|
||||
toml::CoordinatesThree::Coords(c) => Ok((*c).into()),
|
||||
toml::CoordinatesThree::Label(l) => {
|
||||
syntax::CoordinatesThree::Coords(c) => Ok((*c).into()),
|
||||
syntax::CoordinatesThree::Label(l) => {
|
||||
if cycle_detector.contains(l) {
|
||||
bail!(
|
||||
"Found coordinate cycle: `{}`",
|
||||
|
@ -126,19 +124,19 @@ fn resolve_coordinates(
|
|||
}
|
||||
}
|
||||
|
||||
/// Given an object, resolve it's position as a Point3.
|
||||
/// Given an object, resolve its position as a Point3.
|
||||
fn resolve_position(
|
||||
objects: &HashMap<String, toml::Object>,
|
||||
obj: &toml::Object,
|
||||
objects: &HashMap<String, syntax::Object>,
|
||||
obj: &syntax::Object,
|
||||
cycle_detector: HashSet<String>,
|
||||
) -> Result<Point3<f32>> {
|
||||
match &obj.position {
|
||||
toml::Position::Cartesian(c) => Ok(resolve_coordinates(objects, &c, cycle_detector)?),
|
||||
toml::Position::Polar(p) => {
|
||||
syntax::Position::Cartesian(c) => Ok(resolve_coordinates(objects, &c, cycle_detector)?),
|
||||
syntax::Position::Polar(p) => {
|
||||
let three = match &p.center {
|
||||
toml::CoordinatesTwo::Label(s) => toml::CoordinatesThree::Label(s.clone()),
|
||||
toml::CoordinatesTwo::Coords(v) => {
|
||||
toml::CoordinatesThree::Coords([v[0], v[1], f32::NAN])
|
||||
syntax::CoordinatesTwo::Label(s) => syntax::CoordinatesThree::Label(s.clone()),
|
||||
syntax::CoordinatesTwo::Coords(v) => {
|
||||
syntax::CoordinatesThree::Coords([v[0], v[1], f32::NAN])
|
||||
}
|
||||
};
|
||||
let r = resolve_coordinates(&objects, &three, cycle_detector)?;
|
||||
|
@ -157,26 +155,32 @@ fn resolve_position(
|
|||
}
|
||||
}
|
||||
|
||||
impl System {
|
||||
pub fn parse(value: toml::SystemRoot) -> Result<Self> {
|
||||
impl super::Build for System {
|
||||
fn build(root: &super::syntax::Root) -> Result<Vec<Self>> {
|
||||
let mut out = Vec::new();
|
||||
|
||||
for (system_name, system) in &root.system {
|
||||
let mut objects = Vec::new();
|
||||
|
||||
for (label, obj) in &value.object {
|
||||
for (label, obj) in &system.object {
|
||||
let mut cycle_detector = HashSet::new();
|
||||
cycle_detector.insert(label.to_owned());
|
||||
|
||||
objects.push(Object {
|
||||
sprite: obj.sprite.clone(),
|
||||
position: resolve_position(&value.object, &obj, cycle_detector)
|
||||
position: resolve_position(&system.object, &obj, cycle_detector)
|
||||
.with_context(|| format!("In object {:#?}", label))?,
|
||||
size: obj.size,
|
||||
angle: Deg(obj.angle.unwrap_or(0.0)),
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(Self {
|
||||
name: value.system.name.clone(),
|
||||
out.push(Self {
|
||||
name: system_name.to_owned(),
|
||||
objects,
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(out);
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ pub struct System {
|
|||
}
|
||||
|
||||
impl System {
|
||||
pub fn new(ct: &content::system::System) -> Self {
|
||||
pub fn new(ct: &content::System) -> Self {
|
||||
let mut rng = rand::thread_rng();
|
||||
let sz = consts::STARFIELD_SIZE as f32 / 2.0;
|
||||
let mut s = System {
|
||||
|
|
|
@ -12,7 +12,7 @@ use winit::{
|
|||
};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let content = content::load_content_dir(consts::CONTENT_ROOT)?;
|
||||
let content = content::Content::load_dir(consts::CONTENT_ROOT)?;
|
||||
let game = game::Game::new(content);
|
||||
|
||||
pollster::block_on(run(game))?;
|
||||
|
|
Loading…
Reference in New Issue